From c8766e9fb950f492af772d5211443ba707365add Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 02:40:51 +0900 Subject: [PATCH 01/16] feat: migrate auth and workspace catalog to ServerApi --- crates/client/src/backend_auth.rs | 101 +- crates/client/src/backend_workspace.rs | 102 +- crates/server-api/src/lib.rs | 433 ++++- crates/workspace-server/src/server.rs | 699 ++++--- openapi/server-api.json | 1680 ++++++++++++++++- .../src/lib/generated/repository-api.ts | 2 +- 6 files changed, 2638 insertions(+), 379 deletions(-) diff --git a/crates/client/src/backend_auth.rs b/crates/client/src/backend_auth.rs index 9d0f51ba0..9452ff6ba 100644 --- a/crates/client/src/backend_auth.rs +++ b/crates/client/src/backend_auth.rs @@ -1,9 +1,11 @@ use crate::BackendOrigin; -use serde::Deserialize; use std::fmt; use std::time::Duration; -use server_api::{DeviceLoginPollRequest, DeviceLoginPollStatus, DeviceLoginStartRequest}; +use server_api::{ + DeviceLoginPollRequest, DeviceLoginPollStatus, DeviceLoginStartRequest, RepositoryApiError, + ServerApiClient, +}; pub use server_api::{DeviceLoginPollResponse, DeviceLoginStartResponse}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,21 +21,12 @@ impl BackendAuthTarget { .unwrap_or(base_url); Self { base_url } } - - fn api_url(&self, path: &str) -> String { - format!( - "{}{}", - self.base_url.trim_end_matches('/'), - path.strip_prefix('/') - .map(|path| format!("/{path}")) - .unwrap_or_else(|| path.to_string()) - ) - } } #[derive(Debug)] pub enum BackendAuthClientError { - Http(reqwest::Error), + InvalidTarget(String), + ServerApi(server_api::client_support::ClientError), BackendStatus { status: u16, body: String }, MissingAccessToken, } @@ -41,7 +34,8 @@ pub enum BackendAuthClientError { impl fmt::Display for BackendAuthClientError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Http(error) => write!(f, "Backend auth request failed: {error}"), + Self::InvalidTarget(message) => f.write_str(message), + Self::ServerApi(error) => write!(f, "{error}"), Self::BackendStatus { status, body } => { write!(f, "Backend auth returned HTTP {status}: {body}") } @@ -54,40 +48,38 @@ impl fmt::Display for BackendAuthClientError { impl std::error::Error for BackendAuthClientError {} -impl From for BackendAuthClientError { - fn from(value: reqwest::Error) -> Self { - Self::Http(value) - } +const AUTH_RESPONSE_LIMIT: usize = 2 * 1024 * 1024; + +fn auth_client(target: &BackendAuthTarget) -> Result { + ServerApiClient::builder(&target.base_url) + .map_err(|error| BackendAuthClientError::InvalidTarget(error.to_string()))? + .response_body_limit(AUTH_RESPONSE_LIMIT) + .build() + .map_err(|error| BackendAuthClientError::InvalidTarget(error.to_string())) } pub async fn start_device_login( target: &BackendAuthTarget, client_name: Option<&str>, ) -> Result { - let client = reqwest::Client::new(); - let response = client - .post(target.api_url("/api/auth/device-login/start")) - .json(&DeviceLoginStartRequest { + auth_client(target)? + .auth_device_login_start(DeviceLoginStartRequest { client_name: client_name.map(ToOwned::to_owned), }) - .send() - .await?; - parse_json_response(response).await + .await + .map_err(BackendAuthClientError::ServerApi) } pub async fn poll_device_login( target: &BackendAuthTarget, device_code: &str, ) -> Result { - let client = reqwest::Client::new(); - let response = client - .post(target.api_url("/api/auth/device-login/poll")) - .json(&DeviceLoginPollRequest { + auth_client(target)? + .auth_device_login_poll(DeviceLoginPollRequest { device_code: device_code.to_string(), }) - .send() - .await?; - parse_json_response(response).await + .await + .map_err(BackendAuthClientError::ServerApi) } fn device_login_poll_result( @@ -136,24 +128,13 @@ pub async fn wait_for_device_login( } } -async fn parse_json_response Deserialize<'de>>( - response: reqwest::Response, -) -> Result { - let status = response.status(); - if !status.is_success() { - let body = response.text().await.unwrap_or_default(); - return Err(BackendAuthClientError::BackendStatus { - status: status.as_u16(), - body, - }); - } - Ok(response.json::().await?) -} - #[cfg(test)] mod tests { use super::*; use server_api::DeviceAccessTokenType; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::thread; fn poll_response(status: DeviceLoginPollStatus) -> DeviceLoginPollResponse { DeviceLoginPollResponse { @@ -163,6 +144,34 @@ mod tests { } } + #[tokio::test] + async fn device_login_start_uses_generated_server_api_client() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let base_url = format!("http://{}", listener.local_addr().unwrap()); + let handle = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut request = vec![0; 4096]; + let read = stream.read(&mut request).unwrap(); + let request = String::from_utf8_lossy(&request[..read]); + assert!(request.starts_with("POST /api/auth/device-login/start HTTP/1.1")); + assert!(request.contains(r#"{"client_name":"yoi-cli"}"#)); + let body = r#"{"device_code":"device-secret","user_code":"ABCD-EFGH","verification_uri":"https://yoi.example/login/device","verification_uri_complete":"https://yoi.example/login/device?user_code=ABCD-EFGH","expires_in":600,"interval":5}"#; + write!( + stream, + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body + ) + .unwrap(); + }); + + let response = start_device_login(&BackendAuthTarget::new(base_url), Some("yoi-cli")) + .await + .unwrap(); + assert_eq!(response.user_code, "ABCD-EFGH"); + handle.join().unwrap(); + } + #[test] fn device_login_start_response_enforces_shared_expiry_bounds() { let valid = serde_json::json!({ diff --git a/crates/client/src/backend_workspace.rs b/crates/client/src/backend_workspace.rs index edd6cc75a..d3aaaca6e 100644 --- a/crates/client/src/backend_workspace.rs +++ b/crates/client/src/backend_workspace.rs @@ -1,8 +1,7 @@ use crate::{BackendApiClient, BackendApiClientError}; -use reqwest::Method; use server_api::{ - InitialRepositoryIntent, RepositorySummary, WorkspaceCatalogListResponse, - WorkspaceCreateRequest, WorkspaceCreateResponse, WorkspaceSummary, + InitialRepositoryIntent, RepositorySummary, WorkspaceCreateRequest, WorkspaceCreateResponse, + WorkspaceListQuery, WorkspaceSummary, }; use std::fmt; @@ -31,7 +30,7 @@ pub enum BackendWorkspaceClientError { InvalidTarget(String), Api(BackendApiClientError), Http(reqwest::Error), - RepositoryApi(server_api::client_support::ClientError), + ServerApi(server_api::client_support::ClientError), } impl fmt::Display for BackendWorkspaceClientError { @@ -40,7 +39,7 @@ impl fmt::Display for BackendWorkspaceClientError { Self::InvalidTarget(message) => f.write_str(message), Self::Api(error) => write!(f, "{error}"), Self::Http(error) => write!(f, "{error}"), - Self::RepositoryApi(error) => write!(f, "{error}"), + Self::ServerApi(error) => write!(f, "{error}"), } } } @@ -65,11 +64,11 @@ impl From, ) -> Self { - Self::RepositoryApi(error) + Self::ServerApi(error) } } -fn repository_client_error( +fn server_client_error( backend: &BackendApiClient, error: server_api::client_support::ClientError, ) -> BackendWorkspaceClientError { @@ -88,16 +87,16 @@ fn repository_client_error( origin: backend.origin().clone(), }) } - _ => BackendWorkspaceClientError::RepositoryApi(error), + _ => BackendWorkspaceClientError::ServerApi(error), } } #[derive(Clone, Debug)] -struct RepositoryBearerAuthorizer { +struct ServerBearerAuthorizer { authorization: String, } -impl server_api::client_support::RequestAuthorizer for RepositoryBearerAuthorizer { +impl server_api::client_support::RequestAuthorizer for ServerBearerAuthorizer { fn authorize( &self, _request: server_api::client_support::AuthorizerRequest<'_>, @@ -113,20 +112,31 @@ impl server_api::client_support::RequestAuthorizer for RepositoryBearerAuthorize } } -const REPOSITORY_RESPONSE_LIMIT: usize = 16 * 1024 * 1024; +const SERVER_RESPONSE_LIMIT: usize = 16 * 1024 * 1024; + +fn server_api_client( + backend: &BackendApiClient, +) -> Result, BackendWorkspaceClientError> { + server_api::ServerApiClient::builder(backend.origin().as_str()) + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))? + .client(backend.asynchronous_client()) + .authorizer(ServerBearerAuthorizer { + authorization: backend.authorization_header_value(), + }) + .response_body_limit(SERVER_RESPONSE_LIMIT) + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string())) +} pub fn list_backend_workspaces_blocking( target: &BackendWorkspaceCatalogTarget, ) -> Result, BackendWorkspaceClientError> { let client = BackendApiClient::from_stored_token(&target.base_url)?; - let response = client - .blocking_request( - Method::GET, - &format!("/api/workspaces?limit={DEFAULT_WORKSPACE_LIMIT}"), - )? - .send()?; - client.check_status(response.status())?; - Ok(response.json::()?.0) + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; + runtime.block_on(list_backend_workspaces_with_client(&client)) } pub fn list_backend_workspace_repositories_blocking( @@ -158,19 +168,10 @@ async fn list_backend_workspace_repositories_with_client( "Workspace id returned by Backend is invalid".to_string(), )); } - let client = server_api::ServerApiClient::builder(backend.origin().as_str()) - .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))? - .client(backend.asynchronous_client()) - .authorizer(RepositoryBearerAuthorizer { - authorization: backend.authorization_header_value(), - }) - .response_body_limit(REPOSITORY_RESPONSE_LIMIT) - .build() - .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; - let response = client + let response = server_api_client(backend)? .repository_list(workspace_id.to_owned()) .await - .map_err(|error| repository_client_error(backend, error))?; + .map_err(|error| server_client_error(backend, error))?; if response.workspace_id != workspace_id { return Err(BackendWorkspaceClientError::InvalidTarget( "Repository catalog response does not match the requested Workspace".to_string(), @@ -189,15 +190,13 @@ pub async fn list_backend_workspaces( async fn list_backend_workspaces_with_client( client: &BackendApiClient, ) -> Result, BackendWorkspaceClientError> { - let response = client - .request( - Method::GET, - &format!("/api/workspaces?limit={DEFAULT_WORKSPACE_LIMIT}"), - )? - .send() - .await?; - client.check_status(response.status())?; - Ok(response.json::().await?.0) + let response = server_api_client(client)? + .workspace_catalog_list(WorkspaceListQuery { + limit: Some(DEFAULT_WORKSPACE_LIMIT as u32), + }) + .await + .map_err(|error| server_client_error(client, error))?; + Ok(response.0) } pub async fn create_backend_workspace( @@ -205,13 +204,10 @@ pub async fn create_backend_workspace( request: &CreateBackendWorkspaceRequest, ) -> Result { let client = BackendApiClient::from_stored_token(&target.base_url)?; - let response = client - .request(Method::POST, "/api/workspaces")? - .json(request) - .send() - .await?; - client.check_status(response.status())?; - Ok(response.json::().await?) + server_api_client(&client)? + .workspace_catalog_create(request.clone()) + .await + .map_err(|error| server_client_error(&client, error)) } #[cfg(test)] @@ -292,12 +288,12 @@ mod tests { write!( stream, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", - REPOSITORY_RESPONSE_LIMIT + 1, + SERVER_RESPONSE_LIMIT + 1, ) .unwrap(); stream.flush().unwrap(); let chunk = [b' '; 8192]; - let mut remaining = REPOSITORY_RESPONSE_LIMIT + 1; + let mut remaining = SERVER_RESPONSE_LIMIT + 1; while remaining > 0 { let length = remaining.min(chunk.len()); if stream.write_all(&chunk[..length]).is_err() { @@ -315,10 +311,10 @@ mod tests { assert!( matches!( &error, - BackendWorkspaceClientError::RepositoryApi( + BackendWorkspaceClientError::ServerApi( server_api::client_support::ClientError::Failure( server_api::client_support::ClientFailure::ResponseTooLarge { - limit: REPOSITORY_RESPONSE_LIMIT + limit: SERVER_RESPONSE_LIMIT } ) ) @@ -329,11 +325,11 @@ mod tests { } #[test] - fn repository_client_preserves_authentication_error_taxonomy() { + fn server_client_preserves_authentication_error_taxonomy() { let client = BackendApiClient::from_access_token_for_test("https://backend.example.test", "token") .unwrap(); - let unauthorized = repository_client_error( + let unauthorized = server_client_error( &client, server_api::client_support::ClientError::public( reqwest::StatusCode::UNAUTHORIZED, @@ -349,7 +345,7 @@ mod tests { unauthorized, BackendWorkspaceClientError::Api(BackendApiClientError::Unauthorized { .. }) )); - let forbidden = repository_client_error( + let forbidden = server_client_error( &client, server_api::client_support::ClientError::public( reqwest::StatusCode::FORBIDDEN, diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index 26e94a7f3..dd7166eab 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -22,6 +22,7 @@ pub type ServerApiClientError = client_support::ClientError; /// Error body shared by the existing repository routes and their generated adapters. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +#[serde(deny_unknown_fields)] pub struct RepositoryApiError { pub error: String, pub message: String, @@ -76,6 +77,28 @@ macro_rules! impl_openapi_schema { } impl_openapi_schema!( + AuthBootstrapUserRequest, + AuthPublicConfig, + AuthUserResponse, + DeviceLoginApproveRequest, + DeviceLoginApproveResponse, + DeviceLoginPollRequest, + DeviceLoginPollResponse, + DeviceLoginStartRequest, + DeviceLoginStartResponse, + HealthResponse, + PasskeyLoginOptionsRequest, + PasskeyLoginOptionsResponse, + PasskeyRegistrationOptionsRequest, + PasskeyRegistrationOptionsResponse, + WhoamiResponse, + WorkspaceCreateRequest, + WorkspaceCreateResponse, + WorkspaceDeletionOperationResponse, + WorkspaceDeletionPreflightResponse, + WorkspaceDeletionRequest, + WorkspaceListQuery, + WorkspaceCatalogListResponse, CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse, RepositoryApiError, @@ -92,6 +115,162 @@ pub struct WorkspaceWorkerSessionResponse { #[api(reqwest, axum, openapi)] pub trait ServerApi { + #[get("/health", status = 200, error_status = 400)] + async fn health(&self) -> Result; + + #[get("/api/auth/config", status = 200, error_status = 400)] + async fn auth_config(&self) -> Result; + + #[post( + "/api/auth/bootstrap-user", + status = 200, + error_status = 400, + additional_error_statuses = [500] + )] + async fn auth_bootstrap_user( + &self, + #[body] request: AuthBootstrapUserRequest, + ) -> Result; + + #[post( + "/api/auth/passkeys/registration/options", + status = 200, + error_status = 400, + additional_error_statuses = [500, 502] + )] + async fn auth_passkey_registration_options( + &self, + #[extension] context: ServerRequestContext, + #[body] request: PasskeyRegistrationOptionsRequest, + ) -> Result; + + #[post( + "/api/auth/passkeys/login/options", + status = 200, + error_status = 400, + additional_error_statuses = [500, 502] + )] + async fn auth_passkey_login_options( + &self, + #[extension] context: ServerRequestContext, + #[body] request: PasskeyLoginOptionsRequest, + ) -> Result; + + #[post( + "/api/auth/device-login/start", + status = 200, + error_status = 400, + additional_error_statuses = [500, 502] + )] + async fn auth_device_login_start( + &self, + #[body] request: DeviceLoginStartRequest, + ) -> Result; + + #[post( + "/api/auth/device-login/approve", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 500, 502], + bearer_auth = true, + browser_auth = true + )] + async fn auth_device_login_approve( + &self, + #[extension] context: ServerRequestContext, + #[body] request: DeviceLoginApproveRequest, + ) -> Result; + + #[post( + "/api/auth/device-login/poll", + status = 200, + error_status = 400, + additional_error_statuses = [500, 502] + )] + async fn auth_device_login_poll( + &self, + #[body] request: DeviceLoginPollRequest, + ) -> Result; + + #[get("/api/auth/whoami", status = 200, error_status = 400)] + async fn auth_whoami( + &self, + #[extension] context: ServerRequestContext, + ) -> Result; + + #[get( + "/api/workspaces", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_catalog_list( + &self, + #[extension] context: ServerRequestContext, + #[query] query: WorkspaceListQuery, + ) -> Result; + + #[post( + "/api/workspaces", + status = 201, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_catalog_create( + &self, + #[extension] context: ServerRequestContext, + #[body] request: WorkspaceCreateRequest, + ) -> Result; + + #[get( + "/api/workspaces/{workspace_id}/deletion", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_deletion_preflight( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/workspaces/{workspace_id}/deletion", + status = 202, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_deletion_start( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: WorkspaceDeletionRequest, + ) -> Result; + + #[get( + "/api/workspace-deletions/{operation_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_deletion_get( + &self, + #[extension] context: ServerRequestContext, + #[path] operation_id: String, + ) -> Result; + #[get( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/session", status = 200, @@ -204,8 +383,30 @@ pub fn canonical_openapi_document() }) } +/// Server-local request context populated by authentication middleware. +/// +/// Generated clients and OpenAPI omit extension parameters; operation implementations use this +/// value instead of re-parsing transport headers inside domain handlers. +#[derive(Debug, Clone)] +pub struct ServerRequestContext { + pub actor: Option, + pub origin: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct HealthResponse { + pub status: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkspaceListQuery { + pub limit: Option, +} + /// Public browser-authentication configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct AuthPublicConfig { @@ -216,7 +417,7 @@ pub struct AuthPublicConfig { } /// Authentication method that established the current request actor. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum ActorAuthMethod { @@ -225,7 +426,7 @@ pub enum ActorAuthMethod { } /// Public user identity returned by authentication operations. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct AuthenticatedUser { @@ -236,7 +437,7 @@ pub struct AuthenticatedUser { } /// Authenticated actor returned by `GET /api/auth/whoami`. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RequestActor { @@ -258,14 +459,14 @@ impl RequestActor { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WhoamiResponse { pub actor: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct AuthBootstrapUserRequest { @@ -275,14 +476,14 @@ pub struct AuthBootstrapUserRequest { pub display_name: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct AuthUserResponse { pub user: AuthenticatedUser, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct PasskeyRegistrationOptionsRequest { @@ -295,12 +496,13 @@ pub struct PasskeyRegistrationOptionsRequest { pub browser_origin: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct PasskeyRegistrationOptionsResponse { pub challenge_id: String, #[cfg_attr(feature = "typescript", ts(type = "unknown"))] + #[schemars(with = "serde_json::Value")] pub public_key: CreationChallengeResponse, } @@ -313,7 +515,7 @@ pub struct PasskeyRegistrationCompleteRequest { pub credential: RegisterPublicKeyCredential, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct PasskeyLoginOptionsRequest { @@ -325,12 +527,13 @@ pub struct PasskeyLoginOptionsRequest { pub browser_origin: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct PasskeyLoginOptionsResponse { pub challenge_id: String, #[cfg_attr(feature = "typescript", ts(type = "unknown"))] + #[schemars(with = "serde_json::Value")] pub public_key: RequestChallengeResponse, } @@ -343,7 +546,7 @@ pub struct PasskeyLoginCompleteRequest { pub credential: PublicKeyCredential, } -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeviceLoginStartRequest { @@ -355,16 +558,19 @@ pub struct DeviceLoginStartRequest { const DEVICE_LOGIN_EXPIRES_IN_MAX_SECONDS: u64 = 24 * 60 * 60; const DEVICE_LOGIN_POLL_INTERVAL_MAX_SECONDS: u64 = 60; -#[derive(Clone, Serialize, PartialEq, Eq)] +#[derive(Clone, Serialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct DeviceLoginStartResponse { pub device_code: String, pub user_code: String, pub verification_uri: String, pub verification_uri_complete: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 1, max = 86400))] pub expires_in: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 1, max = 60))] pub interval: u64, } @@ -420,21 +626,21 @@ impl std::fmt::Debug for DeviceLoginStartResponse { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeviceLoginApproveRequest { pub user_code: String, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum DeviceLoginApprovalStatus { Approved, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeviceLoginApproveResponse { @@ -442,20 +648,20 @@ pub struct DeviceLoginApproveResponse { pub user: AuthenticatedUser, } -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeviceLoginPollRequest { pub device_code: String, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub enum DeviceAccessTokenType { Bearer, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum DeviceLoginPollStatus { @@ -466,7 +672,7 @@ pub enum DeviceLoginPollStatus { Consumed, } -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeviceLoginPollResponse { @@ -680,7 +886,7 @@ impl RepositoryObservedStatus { } /// Public Workspace catalog item returned by `GET /api/workspaces`. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceSummary { @@ -696,12 +902,12 @@ pub struct WorkspaceSummary { /// /// The transparent newtype keeps the established top-level JSON array while making the /// complete list response a named cross-crate and generated-TypeScript authority. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkspaceCatalogListResponse(pub Vec); /// Public Repository record embedded in Workspace creation responses. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceRepositoryRecord { @@ -712,6 +918,7 @@ pub struct WorkspaceRepositoryRecord { pub source: RepositorySource, pub default_ref: Option, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub source_revision: u64, pub source_fingerprint: String, pub observed_status: RepositoryObservedStatus, @@ -721,7 +928,7 @@ pub struct WorkspaceRepositoryRecord { } /// Initial Repository registration intent for Workspace creation. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct InitialRepositoryIntent { pub repository_key: String, @@ -731,7 +938,7 @@ pub struct InitialRepositoryIntent { } /// Request for atomically creating a Workspace and its initial Repository. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkspaceCreateRequest { pub operation_key: String, @@ -740,18 +947,25 @@ pub struct WorkspaceCreateRequest { } /// Response returned after atomically creating a Workspace and its first Repository. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceCreateResponse { pub workspace: WorkspaceSummary, pub repository: WorkspaceRepositoryRecord, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub config_revision: u64, pub request_fingerprint: String, pub replayed: bool, } +impl api_macros::HttpSuccess for WorkspaceCreateResponse { + fn status_code(&self) -> u16 { + if self.replayed { 200 } else { 201 } + } +} + /// Browser authentication configuration exposed by the scoped Workspace summary. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] @@ -1016,7 +1230,7 @@ where } /// Lifecycle state for one durable Workspace deletion operation. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkspaceDeletionState { @@ -1028,7 +1242,7 @@ pub enum WorkspaceDeletionState { } /// Stable category explaining why Workspace deletion cannot currently advance. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkspaceDeletionBlockerKind { @@ -1042,8 +1256,9 @@ pub enum WorkspaceDeletionBlockerKind { } /// One bounded, user-actionable blocker returned by preflight or execution. -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct WorkspaceDeletionBlocker { pub kind: WorkspaceDeletionBlockerKind, pub resource_kind: Option, @@ -1052,27 +1267,34 @@ pub struct WorkspaceDeletionBlocker { } /// Workspace-owned resources summarized before destructive confirmation. -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceDeletionResourceCounts { #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub workers: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub workdirs: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub repositories: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub runtime_bindings: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub secrets: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub artifacts: u64, } /// Owner-only impact preview for deleting one Workspace. -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct WorkspaceDeletionPreflightResponse { pub workspace_id: String, pub display_name: String, @@ -1084,8 +1306,9 @@ pub struct WorkspaceDeletionPreflightResponse { } /// Idempotent request to start or resume Workspace deletion. -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct WorkspaceDeletionRequest { pub operation_id: String, pub expected_revision: String, @@ -1093,8 +1316,9 @@ pub struct WorkspaceDeletionRequest { } /// Durable deletion operation projection used by request responses and polling. -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct WorkspaceDeletionOperationResponse { pub operation_id: String, pub workspace_id: String, @@ -1109,6 +1333,16 @@ pub struct WorkspaceDeletionOperationResponse { pub completed_at: Option, } +impl api_macros::HttpSuccess for WorkspaceDeletionOperationResponse { + fn status_code(&self) -> u16 { + if self.state == WorkspaceDeletionState::Succeeded { + 200 + } else { + 202 + } + } +} + #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct WorkspaceDeletionBlockerWire { @@ -3881,7 +4115,6 @@ mod tests { #[test] fn worker_session_contract_and_flattened_availability_are_stable() { let operations = ServerApiMetadata::OPERATIONS; - assert_eq!(operations.len(), 6); let operation = operations .iter() .find(|operation| operation.operation_id == "worker_session") @@ -3904,6 +4137,69 @@ mod tests { assert_eq!(value["subject"]["kind"], "runtime_worker"); } + #[test] + fn auth_and_workspace_catalog_operations_are_in_server_api_metadata() { + let operations = ServerApiMetadata::OPERATIONS; + for (operation_id, method, path) in [ + ("health", HttpMethod::Get, "/health"), + ("auth_config", HttpMethod::Get, "/api/auth/config"), + ( + "auth_passkey_registration_options", + HttpMethod::Post, + "/api/auth/passkeys/registration/options", + ), + ( + "auth_passkey_login_options", + HttpMethod::Post, + "/api/auth/passkeys/login/options", + ), + ( + "auth_device_login_start", + HttpMethod::Post, + "/api/auth/device-login/start", + ), + ( + "auth_device_login_approve", + HttpMethod::Post, + "/api/auth/device-login/approve", + ), + ( + "auth_device_login_poll", + HttpMethod::Post, + "/api/auth/device-login/poll", + ), + ("auth_whoami", HttpMethod::Get, "/api/auth/whoami"), + ("workspace_catalog_list", HttpMethod::Get, "/api/workspaces"), + ( + "workspace_catalog_create", + HttpMethod::Post, + "/api/workspaces", + ), + ( + "workspace_deletion_preflight", + HttpMethod::Get, + "/api/workspaces/{workspace_id}/deletion", + ), + ( + "workspace_deletion_start", + HttpMethod::Post, + "/api/workspaces/{workspace_id}/deletion", + ), + ( + "workspace_deletion_get", + HttpMethod::Get, + "/api/workspace-deletions/{operation_id}", + ), + ] { + let operation = operations + .iter() + .find(|operation| operation.operation_id == operation_id) + .unwrap_or_else(|| panic!("missing ServerApi operation {operation_id}")); + assert_eq!(operation.method, method, "{operation_id}"); + assert_eq!(operation.path, path, "{operation_id}"); + } + } + #[test] fn worker_restore_state_round_trips_and_rejects_unknown_variants() { for (state, wire) in [ @@ -5248,6 +5544,75 @@ mod openapi_artifact_tests { ); } + #[test] + fn auth_and_workspace_catalog_contract_is_strict_and_excludes_cookie_routes() { + let document = canonical_openapi_document().expect("canonical OpenAPI contract must build"); + let value: serde_json::Value = + serde_json::from_str(&document.to_json().expect("document must serialize")) + .expect("document must be JSON"); + + for (path, method) in [ + ("/health", "get"), + ("/api/auth/config", "get"), + ("/api/auth/bootstrap-user", "post"), + ("/api/auth/passkeys/registration/options", "post"), + ("/api/auth/passkeys/login/options", "post"), + ("/api/auth/device-login/start", "post"), + ("/api/auth/device-login/approve", "post"), + ("/api/auth/device-login/poll", "post"), + ("/api/auth/whoami", "get"), + ("/api/workspaces", "get"), + ("/api/workspaces", "post"), + ("/api/workspaces/{workspace_id}/deletion", "get"), + ("/api/workspaces/{workspace_id}/deletion", "post"), + ("/api/workspace-deletions/{operation_id}", "get"), + ] { + assert!( + value["paths"][path][method].is_object(), + "missing {method} {path}" + ); + } + let workspace_catalog = &value["paths"]["/api/workspaces"]; + for method in ["get", "post"] { + assert_eq!( + workspace_catalog[method]["security"][0]["bearerAuth"], + serde_json::json!([]) + ); + assert_eq!( + workspace_catalog[method]["security"][1]["browserSession"], + serde_json::json!([]) + ); + } + for status in ["200", "201"] { + assert!(workspace_catalog["post"]["responses"][status].is_object()); + } + let deletion_start = &value["paths"]["/api/workspaces/{workspace_id}/deletion"]["post"]; + for status in ["200", "202", "409"] { + assert!(deletion_start["responses"][status].is_object()); + } + for path in [ + "/api/auth/passkeys/registration/complete", + "/api/auth/passkeys/login/complete", + "/api/auth/logout", + ] { + assert!( + value["paths"].get(path).is_none(), + "cookie response route {path} must remain outside ServerApi" + ); + } + for schema in [ + "RepositoryApiError", + "WorkspaceCreateRequest", + "WorkspaceDeletionRequest", + "DeviceLoginStartResponse", + ] { + assert_eq!( + value["components"]["schemas"][schema]["additionalProperties"], false, + "{schema} must reject unknown fields" + ); + } + } + #[test] fn repository_operations_are_in_the_canonical_contract() { let document = canonical_openapi_document().expect("canonical OpenAPI contract must build"); diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 95af16ff7..0f203c680 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -1132,6 +1132,50 @@ impl From<&WorkspaceApi> for ServerAuthApi { } } +#[derive(Clone)] +enum ServerApiContractService { + Server(WorkspaceServerApi), + Workspace(WorkspaceApi), +} + +impl ServerApiContractService { + fn auth_api(&self) -> ServerAuthApi { + match self { + Self::Server(api) => ServerAuthApi { + config: api.template.as_ref().clone(), + store: api.store.clone(), + }, + Self::Workspace(api) => ServerAuthApi::from(api), + } + } + + fn server_api( + &self, + ) -> std::result::Result<&WorkspaceServerApi, server_api::RepositoryApiError> { + match self { + Self::Server(api) => Ok(api), + Self::Workspace(_) => Err(server_api::RepositoryApiError::new( + StatusCode::NOT_FOUND.as_u16(), + "Not Found", + "Server catalog operation is unavailable on a Workspace-local router", + Vec::new(), + )), + } + } + + fn workspace_api(&self) -> std::result::Result<&WorkspaceApi, server_api::RepositoryApiError> { + match self { + Self::Workspace(api) => Ok(api), + Self::Server(_) => Err(server_api::RepositoryApiError::new( + StatusCode::NOT_FOUND.as_u16(), + "Not Found", + "Workspace-local operation is unavailable on the Server catalog router", + Vec::new(), + )), + } + } +} + #[derive(Clone)] struct WorkerRemovalService { workspace_id: String, @@ -1990,173 +2034,6 @@ fn forbidden_server_response(message: &str) -> Response { .into_response() } -#[derive(Debug, Deserialize)] -struct WorkspaceListQuery { - limit: Option, -} - -async fn server_health() -> Json { - Json(serde_json::json!({ "status": "ok" })) -} - -async fn list_server_workspaces( - State(api): State, - headers: HeaderMap, - Query(query): Query, -) -> Response { - let owner_account_id = match resolve_server_actor(&api, &headers).await { - Ok(Some(actor)) => actor.account_id, - Ok(None) => match api.catalog.is_empty() { - Ok(true) => { - return Json(WorkspaceCatalogListResponse(Vec::new())).into_response(); - } - Ok(false) => return StatusCode::UNAUTHORIZED.into_response(), - Err(error) => return server_error_response(error), - }, - Err(error) => return server_error_response(error), - }; - match api - .catalog - .list(&owner_account_id, query.limit.unwrap_or(100)) - { - Ok(workspaces) => Json(WorkspaceCatalogListResponse( - workspaces.into_iter().map(workspace_summary).collect(), - )) - .into_response(), - Err(error) => server_error_response(error), - } -} - -async fn create_server_workspace( - State(api): State, - headers: HeaderMap, - Json(request): Json, -) -> Response { - let owner_account_id = match resolve_server_actor(&api, &headers).await { - Ok(Some(actor)) => actor.account_id, - Ok(None) => { - return forbidden_server_response("Workspace creation requires an authenticated owner"); - } - Err(error) => return server_error_response(error), - }; - let created = match api.catalog.create(request, owner_account_id) { - Ok(created) => created, - Err(error) => return server_error_response(error), - }; - if let Err(error) = api - .router_for_workspace(&created.workspace.workspace_id) - .await - { - return server_error_response(error); - } - let status = if created.replayed { - StatusCode::OK - } else { - StatusCode::CREATED - }; - (status, Json(workspace_create_response(created))).into_response() -} - -async fn preflight_server_workspace_deletion( - State(api): State, - AxumPath(workspace_id): AxumPath, - headers: HeaderMap, -) -> Response { - let actor_account_id = match resolve_server_actor(&api, &headers).await { - Ok(Some(actor)) => actor.account_id, - Ok(None) => return forbidden_server_response("Workspace deletion requires its owner"), - Err(error) => return server_error_response(error), - }; - match api - .workspace_deletion_preflight(&actor_account_id, &workspace_id) - .await - { - Ok(preflight) => Json(preflight).into_response(), - Err(error) => server_error_response(error), - } -} - -async fn start_server_workspace_deletion( - State(api): State, - AxumPath(workspace_id): AxumPath, - headers: HeaderMap, - Json(request): Json, -) -> Response { - let actor_account_id = match resolve_server_actor(&api, &headers).await { - Ok(Some(actor)) => actor.account_id, - Ok(None) => return forbidden_server_response("Workspace deletion requires its owner"), - Err(error) => return server_error_response(error), - }; - let mutation_lock = api.mutation_lock(&workspace_id).await; - let _mutation_guard = mutation_lock.lock().await; - let existing = match api - .store - .workspace_deletion_operation(&actor_account_id, &request.operation_id) - { - Ok(existing) => existing, - Err(error) => return server_error_response(error), - }; - if existing.is_none() { - let preflight = match api - .workspace_deletion_preflight(&actor_account_id, &workspace_id) - .await - { - Ok(preflight) => preflight, - Err(error) => return server_error_response(error), - }; - if !preflight.can_delete { - return (StatusCode::CONFLICT, Json(preflight)).into_response(); - } - } - let reservation = - match api - .store - .reserve_workspace_deletion(&actor_account_id, &workspace_id, &request) - { - Ok(reservation) => reservation, - Err(error) => return server_error_response(error), - }; - if let Some(handle) = api.hook_handles.lock().await.remove(&workspace_id) { - handle.abort(); - } - let operation = reservation.operation; - if operation.state != WorkspaceDeletionState::Succeeded { - api.schedule_workspace_deletion(request.operation_id).await; - } - let status = if operation.state == WorkspaceDeletionState::Succeeded { - StatusCode::OK - } else { - StatusCode::ACCEPTED - }; - (status, Json(operation)).into_response() -} - -async fn get_server_workspace_deletion( - State(api): State, - AxumPath(operation_id): AxumPath, - headers: HeaderMap, -) -> Response { - let actor_account_id = match resolve_server_actor(&api, &headers).await { - Ok(Some(actor)) => actor.account_id, - Ok(None) => { - return forbidden_server_response("Workspace deletion status requires its owner"); - } - Err(error) => return server_error_response(error), - }; - match api - .store - .workspace_deletion_operation(&actor_account_id, &operation_id) - { - Ok(Some(operation)) => Json(operation).into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - "Workspace deletion operation not found", - ) - .into_response(), - Err(error) => server_error_response(error), - } -} - fn workspace_summary(record: WorkspaceRecord) -> WorkspaceSummary { WorkspaceSummary { workspace_id: record.workspace_id, @@ -2666,22 +2543,17 @@ pub async fn build_workspace_server_router( let api = WorkspaceServerApi::new(template, store); api.preload().await?; api.recover_workspace_deletions().await?; + let contract_service = ServerApiContractService::Server(api.clone()); let catalog = Router::new() - .route("/health", get(server_health)) - .route( - "/api/workspaces", - get(list_server_workspaces).post(create_server_workspace), - ) - .route( - "/api/workspaces/{workspace_id}/deletion", - get(preflight_server_workspace_deletion).post(start_server_workspace_deletion), - ) - .route( - "/api/workspace-deletions/{operation_id}", - get(get_server_workspace_deletion), - ) .fallback(dispatch_workspace_request) - .with_state(api.clone()); + .with_state(api.clone()) + .merge(server_api::server_api_axum::health(Arc::new( + contract_service.clone(), + ))) + .merge(generated_auth_contract_router(contract_service.clone())) + .merge(generated_workspace_catalog_contract_router( + contract_service, + )); Ok(auth .merge(catalog) .layer(axum::middleware::from_fn_with_state( @@ -3752,39 +3624,383 @@ fn configured_repository_from_record(record: RepositoryRecord) -> Result 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)) .with_state(api) } -impl server_api::ServerApi for WorkspaceApi { +async fn attach_server_origin_context(mut request: Request, next: Next) -> Response { + let actor = request.extensions().get::().cloned(); + let origin = request_origin(request.headers()); + request + .extensions_mut() + .insert(server_api::ServerRequestContext { actor, origin }); + next.run(request).await +} + +async fn attach_server_request_context( + State(service): State, + mut request: Request, + next: Next, +) -> Response { + let actor = if let Some(actor) = request.extensions().get::().cloned() { + Some(actor) + } else { + let auth = service.auth_api(); + match resolve_request_actor( + auth.store.as_ref(), + request.headers(), + &auth_public_config(&auth.config).cookie_name, + ) + .await + { + Ok(actor) => actor, + Err(error) => return server_error_response(error), + } + }; + let origin = request_origin(request.headers()); + request + .extensions_mut() + .insert(server_api::ServerRequestContext { actor, origin }); + next.run(request).await +} + +fn generated_auth_contract_router(service: ServerApiContractService) -> Router { + let service = Arc::new(service); + let origin_context = Router::new() + .merge(server_api::server_api_axum::auth_passkey_registration_options(service.clone())) + .merge(server_api::server_api_axum::auth_passkey_login_options( + service.clone(), + )) + .layer(middleware::from_fn(attach_server_origin_context)); + let actor_context = Router::new() + .merge(server_api::server_api_axum::auth_device_login_approve( + service.clone(), + )) + .merge(server_api::server_api_axum::auth_whoami(service.clone())) + .layer(middleware::from_fn_with_state( + service.as_ref().clone(), + attach_server_request_context, + )); + Router::new() + .merge(server_api::server_api_axum::auth_config(service.clone())) + .merge(server_api::server_api_axum::auth_bootstrap_user( + service.clone(), + )) + .merge(server_api::server_api_axum::auth_device_login_start( + service.clone(), + )) + .merge(server_api::server_api_axum::auth_device_login_poll(service)) + .merge(origin_context) + .merge(actor_context) +} + +fn generated_workspace_catalog_contract_router(service: ServerApiContractService) -> Router { + let service = Arc::new(service); + Router::new() + .merge(server_api::server_api_axum::workspace_catalog_list( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_catalog_create( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_deletion_preflight( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_deletion_start( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_deletion_get( + service.clone(), + )) + .layer(middleware::from_fn_with_state( + service.as_ref().clone(), + attach_server_request_context, + )) +} + +fn generated_workspace_contract_router(service: ServerApiContractService) -> Router { + let service = Arc::new(service); + Router::new() + .merge(server_api::server_api_axum::worker_session(service.clone())) + .merge(server_api::server_api_axum::repository_list( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_list_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_detail( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_detail_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_create(service)) +} + +impl server_api::ServerApi for ServerApiContractService { + async fn health( + &self, + ) -> std::result::Result { + Ok(server_api::HealthResponse { + status: "ok".to_string(), + }) + } + + async fn auth_config( + &self, + ) -> std::result::Result { + Ok(auth_public_config(&self.auth_api().config)) + } + + async fn auth_bootstrap_user( + &self, + request: AuthBootstrapUserRequest, + ) -> std::result::Result { + post_auth_bootstrap_user(State(self.auth_api()), Json(request)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_passkey_registration_options( + &self, + context: server_api::ServerRequestContext, + mut request: PasskeyRegistrationOptionsRequest, + ) -> std::result::Result + { + if request.browser_origin.is_none() { + request.browser_origin = context.origin; + } + post_passkey_registration_options(State(self.auth_api()), HeaderMap::new(), Json(request)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_passkey_login_options( + &self, + context: server_api::ServerRequestContext, + mut request: PasskeyLoginOptionsRequest, + ) -> std::result::Result { + if request.browser_origin.is_none() { + request.browser_origin = context.origin; + } + post_passkey_login_options(State(self.auth_api()), HeaderMap::new(), Json(request)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_device_login_start( + &self, + request: DeviceLoginStartRequest, + ) -> std::result::Result { + post_device_login_start(State(self.auth_api()), Json(request)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_device_login_approve( + &self, + context: server_api::ServerRequestContext, + request: DeviceLoginApproveRequest, + ) -> std::result::Result { + let actor = context.actor.ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::UNAUTHORIZED.as_u16(), + "Unauthorized", + "request requires a browser session or Bearer API token", + Vec::new(), + ) + })?; + approve_device_login(&self.auth_api(), actor, request) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_device_login_poll( + &self, + request: DeviceLoginPollRequest, + ) -> std::result::Result { + post_device_login_poll(State(self.auth_api()), Json(request)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn auth_whoami( + &self, + context: server_api::ServerRequestContext, + ) -> std::result::Result { + Ok(WhoamiResponse { + actor: context.actor, + }) + } + + async fn workspace_catalog_list( + &self, + context: server_api::ServerRequestContext, + query: server_api::WorkspaceListQuery, + ) -> std::result::Result { + let api = self.server_api()?; + let Some(actor) = context.actor else { + return if api + .catalog + .is_empty() + .map_err(|error| ApiError::from(error).into_repository_api_error())? + { + Ok(WorkspaceCatalogListResponse(Vec::new())) + } else { + Err(server_api::RepositoryApiError::new( + StatusCode::UNAUTHORIZED.as_u16(), + "Unauthorized", + "authentication required", + Vec::new(), + )) + }; + }; + let workspaces = api + .catalog + .list( + &actor.account_id, + query.limit.unwrap_or(100).try_into().unwrap_or(200), + ) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + Ok(WorkspaceCatalogListResponse( + workspaces.into_iter().map(workspace_summary).collect(), + )) + } + + async fn workspace_catalog_create( + &self, + context: server_api::ServerRequestContext, + request: WorkspaceCreateRequest, + ) -> std::result::Result { + let api = self.server_api()?; + let actor = context.actor.ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::FORBIDDEN.as_u16(), + "Forbidden", + "Workspace creation requires an authenticated owner", + Vec::new(), + ) + })?; + let created = api + .catalog + .create(request, actor.account_id) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + api.router_for_workspace(&created.workspace.workspace_id) + .await + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + Ok(workspace_create_response(created)) + } + + async fn workspace_deletion_preflight( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + ) -> std::result::Result + { + let api = self.server_api()?; + let actor = context.actor.ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::FORBIDDEN.as_u16(), + "Forbidden", + "Workspace deletion requires its owner", + Vec::new(), + ) + })?; + api.workspace_deletion_preflight(&actor.account_id, &workspace_id) + .await + .map_err(|error| ApiError::from(error).into_repository_api_error()) + } + + async fn workspace_deletion_start( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: WorkspaceDeletionRequest, + ) -> std::result::Result + { + let api = self.server_api()?; + let actor = context.actor.ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::FORBIDDEN.as_u16(), + "Forbidden", + "Workspace deletion requires its owner", + Vec::new(), + ) + })?; + let mutation_lock = api.mutation_lock(&workspace_id).await; + let _mutation_guard = mutation_lock.lock().await; + let existing = api + .store + .workspace_deletion_operation(&actor.account_id, &request.operation_id) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + if existing.is_none() { + let preflight = api + .workspace_deletion_preflight(&actor.account_id, &workspace_id) + .await + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + if !preflight.can_delete { + return Err(server_api::RepositoryApiError::new( + StatusCode::CONFLICT.as_u16(), + "Conflict", + "Workspace deletion preflight is blocked", + Vec::new(), + )); + } + } + let reservation = api + .store + .reserve_workspace_deletion(&actor.account_id, &workspace_id, &request) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + if let Some(handle) = api.hook_handles.lock().await.remove(&workspace_id) { + handle.abort(); + } + let operation = reservation.operation; + if operation.state != WorkspaceDeletionState::Succeeded { + api.schedule_workspace_deletion(request.operation_id).await; + } + Ok(operation) + } + + async fn workspace_deletion_get( + &self, + context: server_api::ServerRequestContext, + operation_id: String, + ) -> std::result::Result + { + let api = self.server_api()?; + let actor = context.actor.ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::FORBIDDEN.as_u16(), + "Forbidden", + "Workspace deletion status requires its owner", + Vec::new(), + ) + })?; + api.store + .workspace_deletion_operation(&actor.account_id, &operation_id) + .map_err(|error| ApiError::from(error).into_repository_api_error())? + .ok_or_else(|| { + server_api::RepositoryApiError::new( + StatusCode::NOT_FOUND.as_u16(), + "Not Found", + "Workspace deletion operation not found", + Vec::new(), + ) + }) + } + async fn worker_session( &self, workspace_id: String, @@ -3792,7 +4008,14 @@ impl server_api::ServerApi for WorkspaceApi { worker_id: String, ) -> std::result::Result { - if workspace_id != self.config.workspace_id { + let api = self.workspace_api().map_err(|_| { + runtime_api::RuntimeApiError::new( + StatusCode::NOT_FOUND.as_u16(), + "workspace_not_found", + "Workspace was not found", + ) + })?; + if workspace_id != api.config.workspace_id { return Err(runtime_api::RuntimeApiError::new( StatusCode::NOT_FOUND.as_u16(), "workspace_not_found", @@ -3800,7 +4023,7 @@ impl server_api::ServerApi for WorkspaceApi { )); } let worker = RuntimeWorkerRef::new(runtime_id.clone(), worker_id.clone()); - let observation = self + let observation = api .runtime .worker_session(&workspace_id, &worker) .map_err(|_error| { @@ -3824,20 +4047,18 @@ impl server_api::ServerApi for WorkspaceApi { workspace_id: String, ) -> std::result::Result { - scoped_list_repositories( - State(self.clone()), - AxumPath(ScopedWorkspacePath { workspace_id }), - ) - .await - .map(|Json(response)| response) - .map_err(ApiError::into_repository_api_error) + let api = self.workspace_api()?.clone(); + scoped_list_repositories(State(api), AxumPath(ScopedWorkspacePath { workspace_id })) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) } async fn repository_list_alias( &self, ) -> std::result::Result { - list_repositories(State(self.clone())) + list_repositories(State(self.workspace_api()?.clone())) .await .map(|Json(response)| response) .map_err(ApiError::into_repository_api_error) @@ -3849,8 +4070,9 @@ impl server_api::ServerApi for WorkspaceApi { repository_key: String, ) -> std::result::Result { + let api = self.workspace_api()?.clone(); scoped_repository_detail( - State(self.clone()), + State(api), AxumPath(ScopedRepositoryPath { workspace_id, repository_key, @@ -3866,10 +4088,13 @@ impl server_api::ServerApi for WorkspaceApi { repository_key: String, ) -> std::result::Result { - repository_detail(State(self.clone()), AxumPath(repository_key)) - .await - .map(|Json(response)| response) - .map_err(ApiError::into_repository_api_error) + repository_detail( + State(self.workspace_api()?.clone()), + AxumPath(repository_key), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) } async fn repository_create( @@ -3880,7 +4105,7 @@ impl server_api::ServerApi for WorkspaceApi { ) -> std::result::Result { scoped_create_repository( - State(self.clone()), + State(self.workspace_api()?.clone()), AxumPath(ScopedWorkspacePath { workspace_id }), Extension(actor), Json(request), @@ -3892,7 +4117,9 @@ impl server_api::ServerApi for WorkspaceApi { } fn build_inner_router(api: WorkspaceApi) -> Router { - let auth = build_server_auth_router(ServerAuthApi::from(&api)); + let contract_service = ServerApiContractService::Workspace(api.clone()); + let auth = build_server_auth_router(ServerAuthApi::from(&api)) + .merge(generated_auth_contract_router(contract_service.clone())); let scoped_ticket_relations_query_path = format!("/api/w/{{workspace_id}}{TICKET_RELATIONS_QUERY_PATH}"); let scoped_ticket_orchestration_plans_query_path = @@ -4489,11 +4716,9 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/api/w/{workspace_id}/hosts/{host_id}/workers", get(scoped_list_host_workers), ) - .merge( - server_api::ServerApiAxum::router(api.clone()).with_state::(()), - ) .fallback(get(static_or_spa_fallback)) - .with_state(api); + .with_state(api) + .merge(generated_workspace_contract_router(contract_service)); auth.merge(workspace) .layer(middleware::from_fn(log_failed_api_response)) } @@ -15038,10 +15263,6 @@ async fn scoped_list_host_workers( list_host_workers(State(api), AxumPath(path.host_id)).await } -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, @@ -15387,12 +15608,11 @@ async fn post_device_login_start( .into()) } -async fn post_device_login_approve( - State(api): State, - headers: HeaderMap, - Json(request): Json, -) -> ApiResult> { - let actor = require_actor(&api, &headers).await?; +fn approve_device_login( + api: &ServerAuthApi, + actor: RequestActor, + request: DeviceLoginApproveRequest, +) -> ApiResult { let flow = api .store .get_device_login_flow_by_user_code(&request.user_code.trim().to_ascii_uppercase())? @@ -15441,10 +15661,10 @@ async fn post_device_login_approve( ) .into()); } - Ok(Json(DeviceLoginApproveResponse { + Ok(DeviceLoginApproveResponse { status: DeviceLoginApprovalStatus::Approved, user: actor.user(), - })) + }) } async fn post_device_login_poll( @@ -15488,15 +15708,6 @@ async fn post_device_login_poll( })) } -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, diff --git a/openapi/server-api.json b/openapi/server-api.json index 4e45d76b0..cfc6c7ef9 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -1,6 +1,94 @@ { "components": { "schemas": { + "ActorAuthMethod": { + "description": "Authentication method that established the current request actor.", + "enum": [ + "browser_session", + "api_token" + ], + "type": "string" + }, + "AuthBootstrapUserRequest": { + "additionalProperties": false, + "properties": { + "display_name": { + "type": [ + "string", + "null" + ] + }, + "handle": { + "type": "string" + } + }, + "required": [ + "handle" + ], + "type": "object" + }, + "AuthPublicConfig": { + "additionalProperties": false, + "description": "Public browser-authentication configuration.", + "properties": { + "cookie_name": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "public_base_url": { + "type": "string" + }, + "rp_id": { + "type": "string" + } + }, + "required": [ + "rp_id", + "origin", + "public_base_url", + "cookie_name" + ], + "type": "object" + }, + "AuthUserResponse": { + "additionalProperties": false, + "properties": { + "user": { + "$ref": "#/components/schemas/AuthenticatedUser" + } + }, + "required": [ + "user" + ], + "type": "object" + }, + "AuthenticatedUser": { + "additionalProperties": false, + "description": "Public user identity returned by authentication operations.", + "properties": { + "account_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "handle": { + "type": "string" + }, + "user_id": { + "type": "string" + } + }, + "required": [ + "user_id", + "account_id", + "handle", + "display_name" + ], + "type": "object" + }, "CreateWorkspaceRepositoryRequest": { "additionalProperties": false, "description": "Browser/user intent for registering one Repository in a Workspace.\n\nThe Server parses and canonicalizes `source`; callers cannot assert a\ntransport classification or supply credential material through this DTO.", @@ -44,6 +132,146 @@ ], "type": "object" }, + "DeviceAccessTokenType": { + "enum": [ + "Bearer" + ], + "type": "string" + }, + "DeviceLoginApprovalStatus": { + "enum": [ + "approved" + ], + "type": "string" + }, + "DeviceLoginApproveRequest": { + "additionalProperties": false, + "properties": { + "user_code": { + "type": "string" + } + }, + "required": [ + "user_code" + ], + "type": "object" + }, + "DeviceLoginApproveResponse": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/components/schemas/DeviceLoginApprovalStatus" + }, + "user": { + "$ref": "#/components/schemas/AuthenticatedUser" + } + }, + "required": [ + "status", + "user" + ], + "type": "object" + }, + "DeviceLoginPollRequest": { + "additionalProperties": false, + "properties": { + "device_code": { + "type": "string" + } + }, + "required": [ + "device_code" + ], + "type": "object" + }, + "DeviceLoginPollResponse": { + "additionalProperties": false, + "properties": { + "access_token": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/DeviceLoginPollStatus" + }, + "token_type": { + "anyOf": [ + { + "$ref": "#/components/schemas/DeviceAccessTokenType" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "DeviceLoginPollStatus": { + "enum": [ + "pending", + "approved", + "expired", + "denied", + "consumed" + ], + "type": "string" + }, + "DeviceLoginStartRequest": { + "additionalProperties": false, + "properties": { + "client_name": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "DeviceLoginStartResponse": { + "additionalProperties": false, + "properties": { + "device_code": { + "type": "string" + }, + "expires_in": { + "format": "uint64", + "maximum": 86400, + "minimum": 1, + "type": "integer" + }, + "interval": { + "format": "uint64", + "maximum": 60, + "minimum": 1, + "type": "integer" + }, + "user_code": { + "type": "string" + }, + "verification_uri": { + "type": "string" + }, + "verification_uri_complete": { + "type": "string" + } + }, + "required": [ + "device_code", + "user_code", + "verification_uri", + "verification_uri_complete", + "expires_in", + "interval" + ], + "type": "object" + }, "Diagnostic": { "additionalProperties": false, "properties": { @@ -123,7 +351,114 @@ ], "type": "object" }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "InitialRepositoryIntent": { + "additionalProperties": false, + "description": "Initial Repository registration intent for Workspace creation.", + "properties": { + "default_ref": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "repository_key", + "uri" + ], + "type": "object" + }, + "PasskeyLoginOptionsRequest": { + "additionalProperties": false, + "properties": { + "browser_origin": { + "type": [ + "string", + "null" + ] + }, + "handle": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "PasskeyLoginOptionsResponse": { + "additionalProperties": false, + "properties": { + "challenge_id": { + "type": "string" + }, + "public_key": true + }, + "required": [ + "challenge_id", + "public_key" + ], + "type": "object" + }, + "PasskeyRegistrationOptionsRequest": { + "additionalProperties": false, + "properties": { + "browser_origin": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "handle": { + "type": "string" + } + }, + "required": [ + "handle" + ], + "type": "object" + }, + "PasskeyRegistrationOptionsResponse": { + "additionalProperties": false, + "properties": { + "challenge_id": { + "type": "string" + }, + "public_key": true + }, + "required": [ + "challenge_id", + "public_key" + ], + "type": "object" + }, "RepositoryApiError": { + "additionalProperties": false, "description": "Error body shared by the existing repository routes and their generated adapters.", "properties": { "diagnostics": { @@ -334,6 +669,449 @@ ], "type": "object" }, + "RequestActor": { + "additionalProperties": false, + "description": "Authenticated actor returned by `GET /api/auth/whoami`.", + "properties": { + "account_id": { + "type": "string" + }, + "auth_method": { + "$ref": "#/components/schemas/ActorAuthMethod" + }, + "display_name": { + "type": "string" + }, + "handle": { + "type": "string" + }, + "user_id": { + "type": "string" + } + }, + "required": [ + "user_id", + "account_id", + "handle", + "display_name", + "auth_method" + ], + "type": "object" + }, + "WhoamiResponse": { + "additionalProperties": false, + "properties": { + "actor": { + "anyOf": [ + { + "$ref": "#/components/schemas/RequestActor" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "WorkspaceCatalogListResponse": { + "description": "Public response returned by `GET /api/workspaces`.\n\nThe transparent newtype keeps the established top-level JSON array while making the\ncomplete list response a named cross-crate and generated-TypeScript authority.", + "items": { + "$ref": "#/components/schemas/WorkspaceSummary" + }, + "type": "array" + }, + "WorkspaceCreateRequest": { + "additionalProperties": false, + "description": "Request for atomically creating a Workspace and its initial Repository.", + "properties": { + "display_name": { + "type": "string" + }, + "operation_key": { + "type": "string" + }, + "repository": { + "$ref": "#/components/schemas/InitialRepositoryIntent" + } + }, + "required": [ + "operation_key", + "display_name", + "repository" + ], + "type": "object" + }, + "WorkspaceCreateResponse": { + "additionalProperties": false, + "description": "Response returned after atomically creating a Workspace and its first Repository.", + "properties": { + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "replayed": { + "type": "boolean" + }, + "repository": { + "$ref": "#/components/schemas/WorkspaceRepositoryRecord" + }, + "request_fingerprint": { + "type": "string" + }, + "workspace": { + "$ref": "#/components/schemas/WorkspaceSummary" + } + }, + "required": [ + "workspace", + "repository", + "config_revision", + "request_fingerprint", + "replayed" + ], + "type": "object" + }, + "WorkspaceDeletionBlocker": { + "additionalProperties": false, + "description": "One bounded, user-actionable blocker returned by preflight or execution.", + "properties": { + "kind": { + "$ref": "#/components/schemas/WorkspaceDeletionBlockerKind" + }, + "message": { + "type": "string" + }, + "resource_key": { + "type": [ + "string", + "null" + ] + }, + "resource_kind": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "message" + ], + "type": "object" + }, + "WorkspaceDeletionBlockerKind": { + "description": "Stable category explaining why Workspace deletion cannot currently advance.", + "enum": [ + "last_accessible_workspace", + "revision_conflict", + "dirty_workdir", + "worker_removal_blocked", + "workdir_removal_blocked", + "retention_hold", + "cleanup_unavailable" + ], + "type": "string" + }, + "WorkspaceDeletionOperationResponse": { + "additionalProperties": false, + "description": "Durable deletion operation projection used by request responses and polling.", + "properties": { + "blockers": { + "items": { + "$ref": "#/components/schemas/WorkspaceDeletionBlocker" + }, + "type": "array" + }, + "child_operation_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "completed_at": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "failure_category": { + "type": [ + "string", + "null" + ] + }, + "operation_id": { + "type": "string" + }, + "resources": { + "$ref": "#/components/schemas/WorkspaceDeletionResourceCounts" + }, + "state": { + "$ref": "#/components/schemas/WorkspaceDeletionState" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "workspace_id", + "display_name", + "state", + "resources", + "child_operation_ids", + "blockers", + "created_at", + "updated_at" + ], + "type": "object" + }, + "WorkspaceDeletionPreflightResponse": { + "additionalProperties": false, + "description": "Owner-only impact preview for deleting one Workspace.", + "properties": { + "blockers": { + "items": { + "$ref": "#/components/schemas/WorkspaceDeletionBlocker" + }, + "type": "array" + }, + "can_delete": { + "type": "boolean" + }, + "display_name": { + "type": "string" + }, + "expected_revision": { + "description": "Opaque persisted Workspace metadata revision used as a CAS fence.", + "type": "string" + }, + "resources": { + "$ref": "#/components/schemas/WorkspaceDeletionResourceCounts" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "display_name", + "expected_revision", + "can_delete", + "resources", + "blockers" + ], + "type": "object" + }, + "WorkspaceDeletionRequest": { + "additionalProperties": false, + "description": "Idempotent request to start or resume Workspace deletion.", + "properties": { + "confirmation": { + "type": "string" + }, + "expected_revision": { + "type": "string" + }, + "operation_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "expected_revision", + "confirmation" + ], + "type": "object" + }, + "WorkspaceDeletionResourceCounts": { + "additionalProperties": false, + "description": "Workspace-owned resources summarized before destructive confirmation.", + "properties": { + "artifacts": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "repositories": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "runtime_bindings": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "secrets": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workdirs": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workers": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "workers", + "workdirs", + "repositories", + "runtime_bindings", + "secrets", + "artifacts" + ], + "type": "object" + }, + "WorkspaceDeletionState": { + "description": "Lifecycle state for one durable Workspace deletion operation.", + "enum": [ + "queued", + "running", + "blocked", + "failed", + "succeeded" + ], + "type": "string" + }, + "WorkspaceListQuery": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "WorkspaceRepositoryRecord": { + "additionalProperties": false, + "description": "Public Repository record embedded in Workspace creation responses.", + "properties": { + "created_at": { + "type": "string" + }, + "default_ref": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string" + }, + "observed_at": { + "type": [ + "string", + "null" + ] + }, + "observed_status": { + "$ref": "#/components/schemas/RepositoryObservedStatus" + }, + "provider": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/RepositorySource" + }, + "source_fingerprint": { + "type": "string" + }, + "source_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "repository_key", + "kind", + "source", + "source_revision", + "source_fingerprint", + "observed_status", + "created_at", + "updated_at" + ], + "type": "object" + }, + "WorkspaceSummary": { + "additionalProperties": false, + "description": "Public Workspace catalog item returned by `GET /api/workspaces`.", + "properties": { + "created_at": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "owner_account_id": { + "type": "string" + }, + "state": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "owner_account_id", + "display_name", + "state", + "created_at", + "updated_at" + ], + "type": "object" + }, "string": { "type": "string" } @@ -353,10 +1131,424 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:bac8ba9340cf541183e96715dd4ccd8367f965cdcfc398e6d5091f26a903e1ef" + "x-yoi-source-digest": "sha256:b478a5e12b43d9ac1261ba31358a5b80bb31e398bd57eccbd797f5e92c6f48b7" }, "openapi": "3.1.0", "paths": { + "/api/auth/bootstrap-user": { + "post": { + "operationId": "auth_bootstrap_user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthBootstrapUserRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthUserResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/config": { + "get": { + "operationId": "auth_config", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthPublicConfig" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/device-login/approve": { + "post": { + "operationId": "auth_device_login_approve", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginApproveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginApproveResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/auth/device-login/poll": { + "post": { + "operationId": "auth_device_login_poll", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginPollRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginPollResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/device-login/start": { + "post": { + "operationId": "auth_device_login_start", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginStartRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLoginStartResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/passkeys/login/options": { + "post": { + "operationId": "auth_passkey_login_options", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PasskeyLoginOptionsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PasskeyLoginOptionsResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/passkeys/registration/options": { + "post": { + "operationId": "auth_passkey_registration_options", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PasskeyRegistrationOptionsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PasskeyRegistrationOptionsResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/auth/whoami": { + "get": { + "operationId": "auth_whoami", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhoamiResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/repositories": { "get": { "operationId": "repository_list_alias", @@ -847,6 +2039,492 @@ } ] } + }, + "/api/workspace-deletions/{operation_id}": { + "get": { + "operationId": "workspace_deletion_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "operation_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceDeletionOperationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/workspaces": { + "get": { + "operationId": "workspace_catalog_list", + "parameters": [ + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/WorkspaceListQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceCatalogListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "workspace_catalog_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceCreateResponse" + } + } + }, + "description": "Alternate successful response" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceCreateResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/workspaces/{workspace_id}/deletion": { + "get": { + "operationId": "workspace_deletion_preflight", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceDeletionPreflightResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "workspace_deletion_start", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceDeletionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceDeletionOperationResponse" + } + } + }, + "description": "Alternate successful response" + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceDeletionOperationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/health": { + "get": { + "operationId": "health", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } } } } diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index aa6a289fa..93cb65c48 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:bac8ba9340cf541183e96715dd4ccd8367f965cdcfc398e6d5091f26a903e1ef) +// Input contract: 0.1.0 (sha256:b478a5e12b43d9ac1261ba31358a5b80bb31e398bd57eccbd797f5e92c6f48b7) // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Output: web/workspace/src/lib/generated/repository-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check From 6cbc3a698867d96704adae24f78368be411a3137 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 04:27:52 +0900 Subject: [PATCH 02/16] feat: migrate workspace configuration domains to ServerApi --- Cargo.lock | 1 + crates/client/src/backend_workspace.rs | 39 + crates/client/src/workspace_product.rs | 15 +- crates/memory/src/audit.rs | 53 +- crates/memory/src/backend.rs | 32 +- crates/memory/src/extract/payload.rs | 3 +- crates/memory/src/schema/common.rs | 13 +- crates/memory/src/schema/mod.rs | 1 + crates/server-api/Cargo.toml | 1 + crates/server-api/src/lib.rs | 1249 ++- crates/worker-runtime/src/worker_source.rs | 17 +- crates/worker/src/feature/builtin/memory.rs | 16 +- crates/worker/src/skill.rs | 18 +- crates/worker/src/worker.rs | 14 +- crates/workspace-server/src/server.rs | 986 ++- openapi/server-api.json | 7244 ++++++++++++++++- web/workspace/deno.json | 2 +- .../src/lib/generated/legacy-server-api.ts | 549 +- web/workspace/src/lib/generated/memory-api.ts | 104 +- .../lib/generated/repository-access-api.ts | 89 +- .../src/lib/generated/repository-api.ts | 2 +- web/workspace/src/lib/generated/skill-api.ts | 68 +- .../workspace/api/repository-access-loader.ts | 11 +- .../lib/workspace/api/repository-access.ts | 33 +- .../src/lib/workspace/api/workspace-model.ts | 61 +- .../src/lib/workspace/config-source/api.ts | 261 +- .../src/lib/workspace/config-source/types.ts | 25 +- web/workspace/src/lib/workspace/memory/api.ts | 45 +- .../src/lib/workspace/settings/profile-api.ts | 28 +- .../src/routes/w/[workspaceId]/+layout.ts | 21 +- .../routes/w/[workspaceId]/memory/+page.ts | 5 + .../w/[workspaceId]/memory/staging/+page.ts | 5 + .../settings/repository-access/+page.svelte | 19 +- web/workspace/test/config-source/api.test.ts | 103 +- 34 files changed, 10679 insertions(+), 454 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01722f689..31f95a8c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4149,6 +4149,7 @@ name = "server-api" version = "0.1.0" dependencies = [ "api-macros", + "memory", "protocol", "runtime-api", "schemars", diff --git a/crates/client/src/backend_workspace.rs b/crates/client/src/backend_workspace.rs index d3aaaca6e..d7564e81f 100644 --- a/crates/client/src/backend_workspace.rs +++ b/crates/client/src/backend_workspace.rs @@ -128,6 +128,45 @@ fn server_api_client( .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string())) } +pub(crate) fn memory_document_blocking( + backend: &BackendApiClient, + workspace_id: &str, +) -> Result { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; + runtime.block_on(async { + server_api_client(backend)? + .memory_document(workspace_id.to_owned()) + .await + .map_err(|error| server_client_error(backend, error)) + }) +} + +pub(crate) fn memory_staging_list_blocking( + backend: &BackendApiClient, + workspace_id: &str, + limit: usize, +) -> Result { + let limit = u32::try_from(limit).map_err(|_| { + BackendWorkspaceClientError::InvalidTarget("Memory staging limit exceeds u32".to_string()) + })?; + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; + runtime.block_on(async { + server_api_client(backend)? + .memory_staging_list( + workspace_id.to_owned(), + server_api::MemoryStagingQuery { limit: Some(limit) }, + ) + .await + .map_err(|error| server_client_error(backend, error)) + }) +} + pub fn list_backend_workspaces_blocking( target: &BackendWorkspaceCatalogTarget, ) -> Result, BackendWorkspaceClientError> { diff --git a/crates/client/src/workspace_product.rs b/crates/client/src/workspace_product.rs index f87e62dab..5d42a8e22 100644 --- a/crates/client/src/workspace_product.rs +++ b/crates/client/src/workspace_product.rs @@ -310,14 +310,14 @@ impl BackendWorkspaceProductClient { } pub fn memory_document(&self) -> Result { - self.get_json("/memory") + crate::backend_workspace::memory_document_blocking(&self.api, &self.workspace_id) } pub fn list_memory_staging( &self, limit: usize, ) -> Result { - self.get_json(&format!("/memory/staging?limit={limit}")) + crate::backend_workspace::memory_staging_list_blocking(&self.api, &self.workspace_id, limit) } pub fn launch_ticket_intake( @@ -813,7 +813,16 @@ mod tests { let error = client.list_memory_staging(10).unwrap_err(); - assert!(matches!(error, BackendWorkspaceClientError::Http(_))); + assert!(matches!( + error, + BackendWorkspaceClientError::ServerApi( + server_api::client_support::ClientError::Failure( + server_api::client_support::ClientFailure::Decode { + kind: server_api::client_support::DecodeKind::Success, + }, + ), + ) + )); assert!( request .recv() diff --git a/crates/memory/src/audit.rs b/crates/memory/src/audit.rs index bd3b1887e..dbbce0b3e 100644 --- a/crates/memory/src/audit.rs +++ b/crates/memory/src/audit.rs @@ -4,6 +4,7 @@ //! backend operation boundary. Repository-local audit logs are not an authority. use chrono::{DateTime, Utc}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -11,7 +12,7 @@ fn is_zero_usize(value: &usize) -> bool { *value == 0 } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AuditWorker { MemoryExtract, @@ -27,7 +28,7 @@ impl AuditWorker { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkerLifecycleStatus { Started, @@ -49,7 +50,7 @@ impl WorkerLifecycleStatus { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AuditTrigger { SessionEnd, @@ -77,7 +78,7 @@ impl AuditTrigger { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AuditStatus { Success, @@ -85,7 +86,7 @@ pub enum AuditStatus { Skipped, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct ModelAudit { #[serde(default, skip_serializing_if = "Option::is_none")] pub ref_: Option, @@ -95,31 +96,39 @@ pub struct ModelAudit { pub model_id: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct UsageAudit { #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub input_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub output_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub total_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub cache_read_input_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub cache_creation_input_tokens: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct ExtractAudit { #[serde(default, skip_serializing_if = "Option::is_none")] pub session_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub segment_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] pub entry_range: Option<[u64; 2]>, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] pub history_range: Option<[u64; 2]>, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub staging_count: usize, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub staging_ids: Vec, @@ -127,13 +136,16 @@ pub struct ExtractAudit { pub staging_paths: Vec, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct ConsolidationAudit { #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub staging_count: usize, #[serde(default, skip_serializing_if = "is_zero_usize")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub invalid_staging_count: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub staging_bytes: u64, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub consumed_staging_ids: Vec, @@ -141,19 +153,25 @@ pub struct ConsolidationAudit { pub operations: OperationCounts, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct OperationCounts { #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub write: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub edit: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub delete: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub drop: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub merge: usize, #[serde(default)] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub trim: usize, } @@ -163,15 +181,17 @@ impl OperationCounts { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct MemorySettingsAudit { pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub settings_revision: u64, pub language: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct WorkerLifecycleAudit { + #[schemars(with = "String")] pub run_id: Uuid, pub worker: AuditWorker, pub status: WorkerLifecycleStatus, @@ -189,7 +209,7 @@ pub struct WorkerLifecycleAudit { pub consolidation: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct RecordOperationAudit { pub op: String, pub status: AuditStatus, @@ -204,7 +224,7 @@ pub struct RecordOperationAudit { pub reason: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct RecordUsageAudit { pub op: String, pub status: AuditStatus, @@ -216,12 +236,13 @@ pub struct RecordUsageAudit { #[serde(default, skip_serializing_if = "Option::is_none")] pub query: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub result_count: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub reason: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "event", rename_all = "snake_case")] pub enum AuditPayload { WorkerLifecycle(WorkerLifecycleAudit), @@ -229,9 +250,11 @@ pub enum AuditPayload { RecordUsage(RecordUsageAudit), } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct AuditEvent { + #[schemars(with = "String")] pub id: Uuid, + #[schemars(with = "String")] pub occurred_at: DateTime, #[serde(flatten)] pub payload: AuditPayload, diff --git a/crates/memory/src/backend.rs b/crates/memory/src/backend.rs index 32d987e57..8b7b8a546 100644 --- a/crates/memory/src/backend.rs +++ b/crates/memory/src/backend.rs @@ -11,7 +11,7 @@ use crate::audit::AuditEvent; use crate::extract::{ExtractedCandidate, ExtractedPayload, StagingEvidence}; use crate::schema::{SourceEvidenceRef, SourceRef}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "operation", rename_all = "snake_case")] pub enum MemoryBackendOperation { Query(MemoryQueryOperation), @@ -26,7 +26,7 @@ pub enum MemoryBackendOperation { StagingClose(MemoryStagingCloseOperation), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "status", rename_all = "snake_case")] pub enum MemoryBackendHttpResponse { Ok { @@ -37,7 +37,7 @@ pub enum MemoryBackendHttpResponse { }, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum MemoryBackendOperationResult { ToolOutput(MemoryToolOutput), @@ -45,14 +45,14 @@ pub enum MemoryBackendOperationResult { StagingWritten(MemoryStagingWriteOutput), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryToolOutput { pub summary: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub content: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryQueryOperation { #[serde(default, skip_serializing_if = "Option::is_none")] pub query: Option, @@ -61,8 +61,10 @@ pub struct MemoryQueryOperation { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryDocumentReadOperation { #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub offset: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: Option, } @@ -74,15 +76,15 @@ pub struct MemoryDocumentUpdateOperation { pub replace_all: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)] pub struct MemoryResidentSummaryOperation {} -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryAppendAuditOperation { pub event: AuditEvent, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryStageCandidateOperation { pub source: SourceRef, pub extract_run_id: String, @@ -93,7 +95,7 @@ pub struct MemoryStageCandidateOperation { pub source_refs: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryStageExtractedOperation { pub source: SourceRef, pub payload: ExtractedPayload, @@ -102,6 +104,7 @@ pub struct MemoryStageExtractedOperation { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryStagingListOperation { #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: Option, } @@ -140,28 +143,31 @@ pub enum MemoryStagingAffectedMemoryOperation { Edit, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct MemoryConsolidateStagingOperation { #[serde(default)] pub force: bool, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryConsolidationOutput { pub status: String, pub summary: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub candidate_count: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub total_bytes: u64, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryBackendAckOutput { pub summary: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MemoryStagingWriteOutput { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub staging_count: usize, pub staging_ids: Vec, } diff --git a/crates/memory/src/extract/payload.rs b/crates/memory/src/extract/payload.rs index 745712855..310ecce13 100644 --- a/crates/memory/src/extract/payload.rs +++ b/crates/memory/src/extract/payload.rs @@ -73,12 +73,13 @@ impl ExtractedPayload { } /// Bounded evidence snippet copied into a flat staging record. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct StagingEvidence { pub id: String, pub kind: EvidenceKind, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] pub entry_range: Option<[u64; 2]>, #[serde(default, skip_serializing_if = "Option::is_none")] pub origin: Option, diff --git a/crates/memory/src/schema/common.rs b/crates/memory/src/schema/common.rs index 5c0ec3cc8..0bc05360d 100644 --- a/crates/memory/src/schema/common.rs +++ b/crates/memory/src/schema/common.rs @@ -4,14 +4,23 @@ use lint_common::RecordLintError; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +/// Schema-only representation for JSON integers that must remain exactly +/// representable by JavaScript consumers. +#[allow(dead_code)] +#[derive(JsonSchema)] +pub struct MemoryJsonSafeU64(#[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] u64); + +pub type JsonSafeU64Pair = [MemoryJsonSafeU64; 2]; + pub use lint_common::Frontmatter; /// Reference to a session-store entry range. Stored in `sources` / /// `last_sources` arrays for traceability back to raw session logs. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)] pub struct SourceRef { pub segment_id: String, /// `[start_entry, end_entry]` inclusive range of session-store entry indices. + #[schemars(with = "crate::schema::JsonSafeU64Pair")] pub range: [u64; 2], } @@ -99,6 +108,7 @@ pub struct EvidenceOrigin { #[serde(default, skip_serializing_if = "Option::is_none")] pub flow_definition_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub flow_definition_revision: Option, } @@ -118,6 +128,7 @@ pub struct SourceEvidenceRef { pub segment_id: Option, /// `[start_entry, end_entry]` inclusive range of session-store entry indices. #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] pub entry_range: Option<[u64; 2]>, /// Host-assigned evidence id within the referenced evidence set. #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/memory/src/schema/mod.rs b/crates/memory/src/schema/mod.rs index 34b75ca8d..695b53c32 100644 --- a/crates/memory/src/schema/mod.rs +++ b/crates/memory/src/schema/mod.rs @@ -8,6 +8,7 @@ mod decision; mod request; mod summary; +pub(crate) use common::JsonSafeU64Pair; pub use common::{ EvidenceKind, EvidenceOrigin, EvidenceOriginKind, Frontmatter, SourceEvidenceRef, SourceRef, split_frontmatter, diff --git a/crates/server-api/Cargo.toml b/crates/server-api/Cargo.toml index d371d6db5..d3b9def43 100644 --- a/crates/server-api/Cargo.toml +++ b/crates/server-api/Cargo.toml @@ -11,6 +11,7 @@ typescript = ["dep:ts-rs", "protocol/typescript"] [dependencies] api-macros = { workspace = true, features = ["reqwest", "axum", "openapi"] } +memory.workspace = true protocol.workspace = true runtime-api.workspace = true schemars.workspace = true diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index dd7166eab..568bab327 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -4,6 +4,8 @@ //! by Rust clients. Runtime-internal projections remain in their owning crates; //! callers must explicitly construct these Server-authoritative resources. +use std::collections::BTreeMap; + use api_macros::api; pub use api_macros::axum as server_support; pub use api_macros::reqwest as client_support; @@ -17,6 +19,11 @@ use webauthn_rs_proto::{ RequestChallengeResponse, }; +#[allow(dead_code)] +#[derive(JsonSchema)] +struct ServerJsonSafeU64(#[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] u64); +type JsonSafeU64Pair = [ServerJsonSafeU64; 2]; + pub type ServerApiError = runtime_api::RuntimeApiError; pub type ServerApiClientError = client_support::ClientError; @@ -104,6 +111,47 @@ impl_openapi_schema!( RepositoryApiError, RepositoryListResponse, RepositoryDetailResponse, + WorkspaceResponse, + WorkspaceMetadataSettingsResponse, + UpdateWorkspaceMetadataRequest, + WorkspaceMetadataMutationResponse, + WorkspaceSigningIdentityResponse, + WorkspaceMemorySettings, + UpdateWorkspaceMemorySettingsRequest, + RepositoryAccessProjection, + RepositorySshCredentialListResponse, + RepositorySshCredential, + CreateRepositorySshCredentialRequest, + GenerateRepositorySshCredentialRequest, + RepositorySshPublicKey, + RotateRepositorySshCredentialRequest, + DeleteRepositorySshCredentialRequest, + RepositorySshHostTrustListResponse, + RepositorySshHostTrust, + PutRepositorySshHostTrustRequest, + RepositorySshHostTrustMutationResponse, + DeleteRepositorySshHostTrustRequest, + WorkspaceConfigTreeResponse, + WorkspacePromptProjection, + ConfigCommitRequest, + ConfigTreeSnapshot, + ConfigEntry, + ProfileSettingsResponse, + FlowSourceListResponse, + FlowSourceRecord, + PutFlowRequest, + FlowSourceResolveRequest, + ResolvedFlowSource, + MemoryDocumentResponse, + MemoryStagingQuery, + MemoryStagingListResponse, + MemoryBackendRequest, + MemoryBackendResponse, + MemoryConsolidateStagingRequest, + MemoryConsolidationResponse, + SkillCatalogResponse, + SkillDetailResponse, + SkillActivationResponse, ); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -271,6 +319,518 @@ pub trait ServerApi { #[path] operation_id: String, ) -> Result; + #[get("/api/workspace", status = 200, error_status = 400)] + async fn workspace_current( + &self, + #[extension] context: ServerRequestContext, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/workspace", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_scoped( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_metadata_settings( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[put( + "/api/w/{workspace_id}/settings", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_metadata_settings_update( + &self, + #[path] workspace_id: String, + #[body] request: UpdateWorkspaceMetadataRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/signing-identity", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_signing_identity( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/settings/signing-identity/provision", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_signing_identity_provision( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/memory", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_memory_settings( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[put( + "/api/w/{workspace_id}/settings/memory", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_memory_settings_update( + &self, + #[path] workspace_id: String, + #[body] request: UpdateWorkspaceMemorySettingsRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/repository-access", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_access_projection( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/repository-access/credentials", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_list( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/settings/repository-access/credentials", + status = 201, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_create( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[body] request: CreateRepositorySshCredentialRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/settings/repository-access/credentials/generate", + status = 201, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_generate( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[body] request: GenerateRepositorySshCredentialRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_get( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] credential_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}", + status = 204, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_delete( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] credential_id: String, + #[body] request: DeleteRepositorySshCredentialRequest, + ) -> Result<(), RepositoryApiError>; + + #[get( + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/public-key", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_public_key( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] credential_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/rotate", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_credential_rotate( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] credential_id: String, + #[body] request: RotateRepositorySshCredentialRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/repository-access/host-trusts", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_host_trust_list( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/settings/repository-access/host-trusts", + status = 201, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_host_trust_put( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[body] request: PutRepositorySshHostTrustRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_host_trust_get( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] host_trust_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}", + status = 204, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_host_trust_delete( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] host_trust_id: String, + #[body] request: DeleteRepositorySshHostTrustRequest, + ) -> Result<(), RepositoryApiError>; + + #[get( + "/api/w/{workspace_id}/config/source-tree", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_config_tree( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/config/projections/prompts", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500] + )] + async fn workspace_prompt_projection( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/config/source-tree/commit", + status = 201, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_config_tree_commit( + &self, + #[path] workspace_id: String, + #[body] request: ConfigCommitRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/config/source-tree/revisions/{revision}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_config_revision( + &self, + #[path] workspace_id: String, + #[path] revision: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/config/source-tree/entries/{path}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_config_entry( + &self, + #[path] workspace_id: String, + #[path] path: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/settings/profiles", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn profile_settings( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/flows", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500] + )] + async fn flow_list( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[put( + "/api/w/{workspace_id}/flows", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500] + )] + async fn flow_put( + &self, + #[path] workspace_id: String, + #[body] request: PutFlowRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/flows/resolve", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500] + )] + async fn flow_resolve( + &self, + #[path] workspace_id: String, + #[body] request: FlowSourceResolveRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/flows/{flow_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500] + )] + async fn flow_get( + &self, + #[path] workspace_id: String, + #[path] flow_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/memory", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn memory_document( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/memory/staging", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn memory_staging_list( + &self, + #[path] workspace_id: String, + #[query] query: MemoryStagingQuery, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/memory/backend", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500] + )] + async fn memory_backend( + &self, + #[path] workspace_id: String, + #[body] request: MemoryBackendRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/memory/consolidation", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503] + )] + async fn memory_consolidation( + &self, + #[path] workspace_id: String, + #[body] request: MemoryConsolidateStagingRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/skills", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn skill_list( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/skills/lint", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn skill_lint( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/skills/{name}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500], + bearer_auth = true, + browser_auth = true + )] + async fn skill_get( + &self, + #[path] workspace_id: String, + #[path] name: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/skills/{name}/activate", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 409, 500] + )] + async fn skill_activate( + &self, + #[path] workspace_id: String, + #[path] name: String, + ) -> Result; + #[get( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/session", status = 200, @@ -967,7 +1527,7 @@ impl api_macros::HttpSuccess for WorkspaceCreateResponse { } /// Browser authentication configuration exposed by the scoped Workspace summary. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub enum WorkspaceAuthConfig { Passkey { @@ -979,7 +1539,7 @@ pub enum WorkspaceAuthConfig { } /// Backend-authoritative permissions for the current Workspace actor. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspacePermissionSummary { @@ -989,7 +1549,7 @@ pub struct WorkspacePermissionSummary { pub delete_workspace: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceExtensionPointState { @@ -998,7 +1558,7 @@ pub struct WorkspaceExtensionPointState { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceExtensionPoints { @@ -1009,7 +1569,7 @@ pub struct WorkspaceExtensionPoints { } /// Scoped Workspace metadata and current-actor permission projection. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceResponse { @@ -1017,6 +1577,7 @@ pub struct WorkspaceResponse { pub display_name: String, pub record_authority: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_i64))] pub schema_version: i64, pub auth: WorkspaceAuthConfig, pub permissions: WorkspacePermissionSummary, @@ -1024,7 +1585,7 @@ pub struct WorkspaceResponse { } /// Workspace display metadata exposed from the Server DB settings authority. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceMetadataSettingsResponse { @@ -1037,7 +1598,7 @@ pub struct WorkspaceMetadataSettingsResponse { } /// Compare-and-swap update for Workspace identity display metadata. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct UpdateWorkspaceMetadataRequest { @@ -1045,7 +1606,7 @@ pub struct UpdateWorkspaceMetadataRequest { pub revision: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceMetadataMutationResponse { @@ -1054,7 +1615,7 @@ pub struct WorkspaceMetadataMutationResponse { } /// Lifecycle state for a Workspace-scoped Ed25519 signing identity. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkspaceSigningIdentityState { @@ -1064,7 +1625,7 @@ pub enum WorkspaceSigningIdentityState { /// Public metadata for a Workspace signing identity. Private material and its /// storage reference are deliberately not part of this wire authority. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceSigningIdentityPublic { @@ -1078,6 +1639,7 @@ pub struct WorkspaceSigningIdentityPublic { #[cfg_attr(feature = "typescript", ts(optional))] pub public_key_fingerprint: Option, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub revision: u64, pub state: WorkspaceSigningIdentityState, pub created_at: String, @@ -1087,7 +1649,7 @@ pub struct WorkspaceSigningIdentityPublic { } /// Copyable public trust bundle consumed by future Runtime enrollment work. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspacePublicIdentityBundle { @@ -1098,10 +1660,11 @@ pub struct WorkspacePublicIdentityBundle { pub public_key: String, pub public_key_fingerprint: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub revision: u64, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceSigningIdentityResponse { @@ -1111,6 +1674,288 @@ pub struct WorkspaceSigningIdentityResponse { pub public_bundle: Option, } +/// Source content kinds accepted by the Workspace configuration tree. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum ConfigContentType { + Decodal, + Text, +} + +/// One source entry in the Workspace configuration tree. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ConfigEntry { + pub path: String, + pub content_type: ConfigContentType, + pub content: String, + pub content_digest: String, +} + +/// Immutable Workspace configuration tree snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ConfigTreeSnapshot { + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub revision: u64, + pub digest: String, + pub entries: BTreeMap, +} + +/// Base-relative change applied by a Workspace configuration commit. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum ConfigTreeChange { + Create { + path: String, + content_type: ConfigContentType, + content: String, + }, + Update { + path: String, + expected_digest: String, + content: String, + }, + Rename { + from: String, + to: String, + expected_digest: String, + }, + Delete { + path: String, + expected_digest: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum ConfigProjectionValidator { + StaticTemplateCatalog { + namespace: String, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + key_aliases: BTreeMap, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ConfigSchemaContribution { + pub provider_id: String, + pub namespace: String, + pub version: String, + pub source: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional))] + pub projection_validator: Option, + pub source_digest: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct WorkspaceConfigSchemaBundle { + pub contributions: Vec, + pub source: String, + pub fingerprint: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ToolchainContract { + pub contract_version: u32, + pub decodal_version: String, + pub schema_version: u32, + pub entrypoints: Vec, + pub import_policy_version: u32, + pub schema_bundle: WorkspaceConfigSchemaBundle, + pub fingerprint: String, +} + +/// Workspace configuration tree plus the exact evaluation contract that produced it. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct WorkspaceConfigTreeResponse { + pub snapshot: ConfigTreeSnapshot, + pub contract: ToolchainContract, + pub projection_digest: String, +} + +/// Compare-and-swap request for applying a complete set of source-tree changes. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ConfigCommitRequest { + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub base_revision: u64, + pub base_digest: String, + pub changes: Vec, + pub entrypoints: Vec, +} + +/// Effective Prompt catalog projected from one immutable Workspace config revision. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkspacePromptProjection { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub config_revision: u64, + pub source_digest: String, + pub projection_digest: String, + pub schema_fingerprint: String, + pub toolchain_fingerprint: String, + pub catalog: EffectivePromptCatalog, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct EffectivePromptCatalog { + pub templates: BTreeMap, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub config_revision: u64, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub source_digest: String, + pub schema_fingerprint: String, + pub toolchain_fingerprint: String, + pub catalog_digest: String, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum FlowSourceKind { + Builtin, + Workspace, +} + +/// Current source record for one Workspace-authored Flow. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct FlowSourceRecord { + pub workspace_id: String, + pub flow_id: String, + pub source_kind: FlowSourceKind, + pub name: String, + pub path: String, + pub content: String, + pub content_digest: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub revision: u64, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct FlowSourceListResponse(pub Vec); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct PutFlowRequest { + pub path: String, + pub content: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct FlowSourceResolveRequest { + pub selector: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct CompiledFlowTransition { + pub id: String, + pub target: String, + pub condition: String, + pub synthetic: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct CompiledFlowState { + pub id: String, + pub instructions: String, + pub terminal: bool, + pub transitions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct CompiledFlowDefinition { + pub schema_version: u32, + pub name: String, + pub initial: String, + pub states: BTreeMap, + pub content_digest: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ResolvedFlowSource { + pub selector: String, + pub workspace_id: String, + pub flow_id: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub revision: u64, + pub content_digest: String, + pub definition: CompiledFlowDefinition, +} + +/// Query parameters for the bounded Memory staging list. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MemoryStagingQuery { + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, +} + +/// Public wrapper retaining the established tagged Memory backend wire shape. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +pub struct MemoryBackendRequest(pub memory::backend::MemoryBackendOperation); + +/// Public wrapper retaining the established tagged Memory backend response shape. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +pub struct MemoryBackendResponse(pub memory::backend::MemoryBackendHttpResponse); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MemoryConsolidateStagingRequest { + #[serde(default)] + pub force: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MemoryConsolidationResponse { + pub status: String, + pub summary: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub candidate_count: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub total_bytes: u64, +} + pub const WORKSPACE_DELETION_MAX_OPERATION_ID_BYTES: usize = 128; pub const WORKSPACE_DELETION_MAX_REVISION_BYTES: usize = 128; pub const WORKSPACE_DELETION_MAX_CONFIRMATION_BYTES: usize = 256; @@ -1467,7 +2312,7 @@ impl<'de> Deserialize<'de> for WorkspaceDeletionOperationResponse { } /// Read-only Profile catalog projected from one active Workspace config revision. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -1476,6 +2321,7 @@ pub struct ProfileSettingsResponse { pub registry_revision: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional, type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub config_revision: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub tree_digest: Option, @@ -1488,7 +2334,7 @@ pub struct ProfileSettingsResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -1506,7 +2352,7 @@ pub struct WorkspaceProfileSummary { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceProfileSourceSummary { @@ -1519,11 +2365,12 @@ pub struct WorkspaceProfileSourceSummary { pub editable: bool, pub revision: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub size_bytes: u64, pub diagnostics: Vec, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkspaceProfileSourceProvenance { @@ -2969,20 +3816,27 @@ pub struct WorkerRestoreResponse { pub result: WorkerRestoreResult, } +pub const MEMORY_API_MAX_RESPONSE_BYTES: usize = 8 * 1024 * 1024; +pub const MEMORY_API_MAX_DOCUMENT_BYTES: usize = 4 * 1024 * 1024; +pub const MEMORY_API_MAX_COLLECTION_ITEMS: usize = 500; +pub const MEMORY_API_MAX_STRING_BYTES: usize = 1024 * 1024; +pub const MEMORY_API_MAX_IDENTIFIER_BYTES: usize = 512; + /// Public Workspace Memory document projection. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemoryDocumentResponse { pub body_md: String, pub created_at: String, pub updated_at: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub bytes: usize, pub record_source: String, } /// Candidate kinds exposed by the Memory staging resource. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum MemoryCandidateKind { @@ -2995,7 +3849,7 @@ pub enum MemoryCandidateKind { } /// Typed, bounded provenance classification for public Memory evidence anchors. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum MemoryEvidenceOriginKind { @@ -3013,7 +3867,7 @@ pub enum MemoryEvidenceOriginKind { /// /// This is provenance only. It carries no message body, prompt, reasoning, /// secret, tool output, or authorization authority. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3033,27 +3887,30 @@ pub struct MemoryEvidenceOrigin { pub flow_definition_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional, type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub flow_definition_revision: Option, } /// Record-level source range for one Memory staging candidate. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemorySourceRef { pub segment_id: String, #[cfg_attr(feature = "typescript", ts(type = "[number, number]"))] + #[schemars(with = "JsonSafeU64Pair")] pub range: [u64; 2], } /// Bounded evidence snippet included in one Memory staging record. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemoryStagingEvidence { pub id: String, pub kind: String, #[cfg_attr(feature = "typescript", ts(type = "[number, number] | null"))] + #[schemars(with = "Option")] pub entry_range: Option<[u64; 2]>, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr( @@ -3066,13 +3923,14 @@ pub struct MemoryStagingEvidence { } /// Bounded source anchor included in one Memory staging record. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemorySourceEvidenceRef { pub session_id: Option, pub segment_id: Option, #[cfg_attr(feature = "typescript", ts(type = "[number, number] | null"))] + #[schemars(with = "Option")] pub entry_range: Option<[u64; 2]>, pub evidence_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -3087,7 +3945,7 @@ pub struct MemorySourceEvidenceRef { } /// Public projection of one valid Memory staging record. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemoryStagingRecord { @@ -3104,24 +3962,29 @@ pub struct MemoryStagingRecord { } /// Public list entry for one valid Memory staging record. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemoryStagingEntry { pub id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub byte_len: u64, pub record: MemoryStagingRecord, } /// Public response returned by the Workspace Memory staging list resource. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct MemoryStagingListResponse { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub returned_count: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub total_valid_count: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub invalid_count: usize, pub truncated: bool, pub order: String, @@ -3150,8 +4013,11 @@ pub fn memory_api_typescript() -> String { MemoryStagingListResponse::decl(&config), ]; + let limits = format!( + "export const MEMORY_API_LIMITS = {{\n maxResponseBytes: {MEMORY_API_MAX_RESPONSE_BYTES},\n maxDocumentBytes: {MEMORY_API_MAX_DOCUMENT_BYTES},\n maxCollectionItems: {MEMORY_API_MAX_COLLECTION_ITEMS},\n maxStringBytes: {MEMORY_API_MAX_STRING_BYTES},\n maxIdentifierBytes: {MEMORY_API_MAX_IDENTIFIER_BYTES},\n}} as const;" + ); format!( - "// Generated from server-api. Do not edit by hand.\n// Regenerate: cargo run -q -p server-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts\n\n{}\n", + "// Generated from server-api. Do not edit by hand.\n// Regenerate: cargo run -q -p server-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts\n\n{limits}\n\n{}\n", declarations .into_iter() .map(|declaration| format!("export {declaration}")) @@ -3161,18 +4027,20 @@ pub fn memory_api_typescript() -> String { } /// Workspace-owned Memory settings returned by the shared Server API. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkspaceMemorySettings { pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub settings_revision: u64, pub language: String, } /// Compare-and-swap update for Workspace-owned Memory settings. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct UpdateWorkspaceMemorySettingsRequest { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: u64, pub language: String, } @@ -3180,7 +4048,7 @@ pub struct UpdateWorkspaceMemorySettingsRequest { /// Public metadata for one Workspace-scoped Repository SSH credential. /// /// Secret references and secret material are deliberately not part of this DTO. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshCredential { @@ -3190,6 +4058,7 @@ pub struct RepositorySshCredential { pub public_key_algorithm: String, pub public_key_fingerprint: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub current_revision: u64, pub status: String, pub created_at: String, @@ -3198,7 +4067,11 @@ pub struct RepositorySshCredential { pub referenced_repositories: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct RepositorySshCredentialListResponse(pub Vec); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct CreateRepositorySshCredentialRequest { @@ -3210,7 +4083,7 @@ pub struct CreateRepositorySshCredentialRequest { pub passphrase: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct GenerateRepositorySshCredentialRequest { @@ -3219,41 +4092,44 @@ pub struct GenerateRepositorySshCredentialRequest { pub name: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshPublicKey { pub credential_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub current_revision: u64, pub public_key_algorithm: String, pub public_key_fingerprint: String, pub public_key: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RotateRepositorySshCredentialRequest { pub operation_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: u64, pub private_key: String, #[serde(default)] pub passphrase: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeleteRepositorySshCredentialRequest { pub operation_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: u64, } /// Public metadata for an explicitly pinned SSH host key. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshHostTrust { @@ -3265,6 +4141,7 @@ pub struct RepositorySshHostTrust { pub host_key: String, pub fingerprint: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub current_revision: u64, pub created_at: String, pub updated_at: String, @@ -3272,7 +4149,11 @@ pub struct RepositorySshHostTrust { pub referenced_repositories: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct RepositorySshHostTrustListResponse(pub Vec); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct PutRepositorySshHostTrustRequest { @@ -3283,19 +4164,37 @@ pub struct PutRepositorySshHostTrustRequest { pub host_key: String, #[serde(default)] #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RepositorySshHostTrustMutationResponse { + #[serde(flatten)] + pub host_trust: RepositorySshHostTrust, + #[serde(skip)] + #[schemars(skip)] + pub created: bool, +} + +impl api_macros::HttpSuccess for RepositorySshHostTrustMutationResponse { + fn status_code(&self) -> u16 { + if self.created { 201 } else { 200 } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct DeleteRepositorySshHostTrustRequest { pub operation_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: u64, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(rename_all = "snake_case"))] #[serde(rename_all = "snake_case")] @@ -3304,7 +4203,7 @@ pub enum RepositoryAccessMode { ReadWrite, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshAccessBinding { @@ -3315,12 +4214,13 @@ pub struct RepositorySshAccessBinding { } /// Secret-free active Repository access projection consumed by later Runtime work. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositoryAccessProjection { pub workspace_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub config_revision: u64, pub projection_digest: String, pub bindings: Vec, @@ -3340,7 +4240,7 @@ pub const SKILL_API_MAX_PATH_BYTES: usize = 1_024; pub const SKILL_API_MAX_DIGEST_BYTES: usize = 128; pub const SKILL_API_MAX_RESPONSE_BYTES: usize = 2_097_152; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(rename_all = "snake_case"))] #[serde(rename_all = "snake_case")] @@ -3349,7 +4249,7 @@ pub enum SkillDiagnosticSeverity { Warning, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillDiagnostic { @@ -3389,7 +4289,7 @@ impl SkillDiagnostic { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(rename_all = "snake_case"))] #[serde(rename_all = "snake_case")] @@ -3398,7 +4298,7 @@ pub enum SkillSourceKind { Workspace, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillProvenance { @@ -3418,7 +4318,7 @@ pub struct SkillProvenance { pub tree_digest: Option, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(rename_all = "snake_case"))] #[serde(rename_all = "snake_case")] @@ -3427,7 +4327,7 @@ pub enum SkillActivationStatus { Inactive, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(rename_all = "snake_case"))] #[serde(rename_all = "snake_case")] @@ -3436,16 +4336,17 @@ pub enum SkillProjectionStatus { Invalid, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillProjectionIdentity { #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub config_revision: u64, pub tree_digest: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillResourceRef { @@ -3457,7 +4358,7 @@ pub struct SkillResourceRef { pub diagnostic: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillCatalogEntry { @@ -3470,7 +4371,7 @@ pub struct SkillCatalogEntry { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillCatalogResponse { @@ -3480,7 +4381,7 @@ pub struct SkillCatalogResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct SkillDetailResponse { @@ -3499,6 +4400,17 @@ pub struct SkillDetailResponse { pub resources: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct SkillActivationResponse { + pub name: String, + pub provenance: SkillProvenance, + #[serde(default)] + pub diagnostics: Vec, + pub body: String, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SkillApiValidationError { CollectionTooLarge, @@ -3770,6 +4682,16 @@ pub fn legacy_catalog_typescript() -> String { WorkspaceSigningIdentityPublic::decl(&config), WorkspacePublicIdentityBundle::decl(&config), WorkspaceSigningIdentityResponse::decl(&config), + ConfigContentType::decl(&config), + ConfigEntry::decl(&config), + ConfigTreeSnapshot::decl(&config), + ConfigTreeChange::decl(&config), + ConfigProjectionValidator::decl(&config), + ConfigSchemaContribution::decl(&config), + WorkspaceConfigSchemaBundle::decl(&config), + ToolchainContract::decl(&config), + WorkspaceConfigTreeResponse::decl(&config), + ConfigCommitRequest::decl(&config), ProfileSettingsResponse::decl(&config), WorkspaceProfileSummary::decl(&config), WorkspaceProfileSourceSummary::decl(&config), @@ -3866,6 +4788,7 @@ pub fn skill_api_typescript() -> String { SkillCatalogEntry::decl(&config), SkillCatalogResponse::decl(&config), SkillDetailResponse::decl(&config), + SkillActivationResponse::decl(&config), ]; let limits = format!( "export const SKILL_API_AUTHORITY = \"{SKILL_CATALOG_AUTHORITY}\" as const;\n\nexport const SKILL_API_LIMITS = {{\n maxSafeInteger: {SKILL_API_MAX_SAFE_INTEGER},\n maxCatalogEntries: {SKILL_API_MAX_CATALOG_ENTRIES},\n maxOverrides: {SKILL_API_MAX_OVERRIDES},\n maxDiagnostics: {SKILL_API_MAX_DIAGNOSTICS},\n maxResources: {SKILL_API_MAX_RESOURCES},\n maxAllowedTools: {SKILL_API_MAX_ALLOWED_TOOLS},\n maxNameBytes: {SKILL_API_MAX_NAME_BYTES},\n maxLabelBytes: {SKILL_API_MAX_LABEL_BYTES},\n maxBodyBytes: {SKILL_API_MAX_BODY_BYTES},\n maxPathBytes: {SKILL_API_MAX_PATH_BYTES},\n maxDigestBytes: {SKILL_API_MAX_DIGEST_BYTES},\n maxResponseBytes: {SKILL_API_MAX_RESPONSE_BYTES},\n}} as const;" @@ -4200,6 +5123,198 @@ mod tests { } } + #[test] + fn workspace_configuration_domain_operations_are_in_server_api_metadata() { + let operations = ServerApiMetadata::OPERATIONS; + for (operation_id, method, path) in [ + ("workspace_current", HttpMethod::Get, "/api/workspace"), + ( + "workspace_scoped", + HttpMethod::Get, + "/api/w/{workspace_id}/workspace", + ), + ( + "workspace_metadata_settings", + HttpMethod::Get, + "/api/w/{workspace_id}/settings", + ), + ( + "workspace_metadata_settings_update", + HttpMethod::Put, + "/api/w/{workspace_id}/settings", + ), + ( + "workspace_signing_identity", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/signing-identity", + ), + ( + "workspace_signing_identity_provision", + HttpMethod::Post, + "/api/w/{workspace_id}/settings/signing-identity/provision", + ), + ( + "workspace_memory_settings", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/memory", + ), + ( + "workspace_memory_settings_update", + HttpMethod::Put, + "/api/w/{workspace_id}/settings/memory", + ), + ( + "repository_access_projection", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access", + ), + ( + "repository_ssh_credential_list", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access/credentials", + ), + ( + "repository_ssh_credential_create", + HttpMethod::Post, + "/api/w/{workspace_id}/settings/repository-access/credentials", + ), + ( + "repository_ssh_credential_generate", + HttpMethod::Post, + "/api/w/{workspace_id}/settings/repository-access/credentials/generate", + ), + ( + "repository_ssh_credential_get", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}", + ), + ( + "repository_ssh_credential_delete", + HttpMethod::Delete, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}", + ), + ( + "repository_ssh_credential_public_key", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/public-key", + ), + ( + "repository_ssh_credential_rotate", + HttpMethod::Post, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/rotate", + ), + ( + "repository_ssh_host_trust_list", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access/host-trusts", + ), + ( + "repository_ssh_host_trust_put", + HttpMethod::Post, + "/api/w/{workspace_id}/settings/repository-access/host-trusts", + ), + ( + "repository_ssh_host_trust_get", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}", + ), + ( + "repository_ssh_host_trust_delete", + HttpMethod::Delete, + "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}", + ), + ( + "workspace_config_tree", + HttpMethod::Get, + "/api/w/{workspace_id}/config/source-tree", + ), + ( + "workspace_prompt_projection", + HttpMethod::Get, + "/api/w/{workspace_id}/config/projections/prompts", + ), + ( + "workspace_config_tree_commit", + HttpMethod::Post, + "/api/w/{workspace_id}/config/source-tree/commit", + ), + ( + "workspace_config_revision", + HttpMethod::Get, + "/api/w/{workspace_id}/config/source-tree/revisions/{revision}", + ), + ( + "workspace_config_entry", + HttpMethod::Get, + "/api/w/{workspace_id}/config/source-tree/entries/{path}", + ), + ( + "profile_settings", + HttpMethod::Get, + "/api/w/{workspace_id}/settings/profiles", + ), + ("flow_list", HttpMethod::Get, "/api/w/{workspace_id}/flows"), + ("flow_put", HttpMethod::Put, "/api/w/{workspace_id}/flows"), + ( + "flow_resolve", + HttpMethod::Post, + "/api/w/{workspace_id}/flows/resolve", + ), + ( + "flow_get", + HttpMethod::Get, + "/api/w/{workspace_id}/flows/{flow_id}", + ), + ( + "memory_document", + HttpMethod::Get, + "/api/w/{workspace_id}/memory", + ), + ( + "memory_staging_list", + HttpMethod::Get, + "/api/w/{workspace_id}/memory/staging", + ), + ( + "memory_backend", + HttpMethod::Post, + "/api/w/{workspace_id}/memory/backend", + ), + ( + "memory_consolidation", + HttpMethod::Post, + "/api/w/{workspace_id}/memory/consolidation", + ), + ( + "skill_list", + HttpMethod::Get, + "/api/w/{workspace_id}/skills", + ), + ( + "skill_lint", + HttpMethod::Get, + "/api/w/{workspace_id}/skills/lint", + ), + ( + "skill_get", + HttpMethod::Get, + "/api/w/{workspace_id}/skills/{name}", + ), + ( + "skill_activate", + HttpMethod::Get, + "/api/w/{workspace_id}/skills/{name}/activate", + ), + ] { + let operation = operations + .iter() + .find(|operation| operation.operation_id == operation_id) + .unwrap_or_else(|| panic!("missing ServerApi operation {operation_id}")); + assert_eq!(operation.method, method, "{operation_id}"); + assert_eq!(operation.path, path, "{operation_id}"); + } + } + #[test] fn worker_restore_state_round_trips_and_rejects_unknown_variants() { for (state, wire) in [ @@ -4892,6 +6007,8 @@ mod tests { assert!(!output.contains("export type RepositoryObservedStatus =")); assert!(output.contains("export type WorkspaceMetadataSettingsResponse =")); assert!(output.contains("export type WorkspaceMetadataMutationResponse =")); + assert!(output.contains("export type WorkspaceConfigTreeResponse =")); + assert!(output.contains("export type ConfigCommitRequest =")); assert!(output.contains("export type ProfileSettingsResponse =")); assert!(output.contains("config_revision?: number | null")); assert!(output.contains("provenance: WorkspaceProfileSourceProvenance")); @@ -4904,6 +6021,34 @@ mod tests { assert!(!output.contains("repository_key: string, display_name")); } + #[cfg(feature = "typescript")] + #[test] + fn generated_legacy_server_api_contract_is_current() { + fn normalize(value: &str) -> String { + value + .chars() + .filter_map(|character| match character { + character if character.is_whitespace() => None, + ',' => Some(';'), + character => Some(character), + }) + .collect::() + .replace("=|", "=") + .replace(";}", "}") + } + + let expected = legacy_catalog_typescript(); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../web/workspace/src/lib/generated/legacy-server-api.ts"); + let actual = std::fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())); + assert_eq!( + normalize(&actual), + normalize(&expected), + "regenerate legacy Server API TypeScript types and format the generated file", + ); + } + #[test] fn worker_resource_key_is_required() { let payload = serde_json::json!({ diff --git a/crates/worker-runtime/src/worker_source.rs b/crates/worker-runtime/src/worker_source.rs index f42aba2a3..dff664a8f 100644 --- a/crates/worker-runtime/src/worker_source.rs +++ b/crates/worker-runtime/src/worker_source.rs @@ -532,18 +532,29 @@ impl WorkspaceClient for RuntimeOwnedWorkspaceClient { response.status, response.body ))); } - let projection: WorkspacePromptProjection = + let wire_projection: server_api::WorkspacePromptProjection = serde_json::from_str(&response.body).map_err(|error| { WorkspaceClientError::Request(format!( "invalid active Workspace Prompt projection response: {error}" )) })?; - if projection.workspace_id != self.workspace_id { + if wire_projection.workspace_id != self.workspace_id { return Err(WorkspaceClientError::Request(format!( "active Workspace Prompt projection scope mismatch: expected {}, got {}", - self.workspace_id, projection.workspace_id + self.workspace_id, wire_projection.workspace_id ))); } + let projection: WorkspacePromptProjection = + serde_json::from_value(serde_json::to_value(wire_projection).map_err(|error| { + WorkspaceClientError::Request(format!( + "project active Workspace Prompt projection response: {error}" + )) + })?) + .map_err(|error| { + WorkspaceClientError::Request(format!( + "convert active Workspace Prompt projection response: {error}" + )) + })?; let resolution = cache .observe(projection) .map_err(WorkspaceClientError::Request)?; diff --git a/crates/worker/src/feature/builtin/memory.rs b/crates/worker/src/feature/builtin/memory.rs index ea4c68b1d..3198b5ea6 100644 --- a/crates/worker/src/feature/builtin/memory.rs +++ b/crates/worker/src/feature/builtin/memory.rs @@ -104,7 +104,7 @@ async fn execute_memory_backend( let response = client.execute(WorkspaceRequest::json( WorkspaceRequestMethod::Post, format!("/api/w/{workspace_id}/memory/backend"), - serde_json::to_string(&operation)?, + serde_json::to_string(&server_api::MemoryBackendRequest(operation))?, ))?; let status = reqwest::StatusCode::from_u16(response.status) .unwrap_or(reqwest::StatusCode::INTERNAL_SERVER_ERROR); @@ -114,7 +114,7 @@ async fn execute_memory_backend( body: response.body, }); } - match serde_json::from_str::(&response.body)? { + match serde_json::from_str::(&response.body)?.0 { MemoryBackendHttpResponse::Ok { result } => Ok(result), MemoryBackendHttpResponse::Error { message } => { Err(WorkspaceMemoryBackendError::Backend(message)) @@ -138,7 +138,9 @@ async fn execute_memory_consolidation( let response = client.execute(WorkspaceRequest::json( WorkspaceRequestMethod::Post, format!("/api/w/{workspace_id}/memory/consolidation"), - serde_json::to_string(&operation)?, + serde_json::to_string(&server_api::MemoryConsolidateStagingRequest { + force: operation.force, + })?, ))?; let status = reqwest::StatusCode::from_u16(response.status) .unwrap_or(reqwest::StatusCode::INTERNAL_SERVER_ERROR); @@ -148,7 +150,13 @@ async fn execute_memory_consolidation( body: response.body, }); } - serde_json::from_str::(&response.body).map_err(Into::into) + let response = serde_json::from_str::(&response.body)?; + Ok(MemoryConsolidationOutput { + status: response.status, + summary: response.summary, + candidate_count: response.candidate_count, + total_bytes: response.total_bytes, + }) } pub fn workspace_http_memory_tools(client: Arc) -> Vec { diff --git a/crates/worker/src/skill.rs b/crates/worker/src/skill.rs index 9eca47898..bdc22c59b 100644 --- a/crates/worker/src/skill.rs +++ b/crates/worker/src/skill.rs @@ -1,23 +1,13 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use crate::worker::WorkspaceClient; pub use server_api::{ - SkillActivationStatus, SkillCatalogEntry, SkillCatalogResponse, SkillDetailResponse, - SkillDiagnostic, SkillDiagnosticSeverity, SkillProjectionIdentity, SkillProjectionStatus, - SkillProvenance, SkillResourceRef, SkillSourceKind, + SkillActivationResponse, SkillActivationStatus, SkillCatalogEntry, SkillCatalogResponse, + SkillDetailResponse, SkillDiagnostic, SkillDiagnosticSeverity, SkillProjectionIdentity, + SkillProjectionStatus, SkillProvenance, SkillResourceRef, SkillSourceKind, }; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SkillActivationResponse { - pub name: String, - pub provenance: SkillProvenance, - #[serde(default)] - pub diagnostics: Vec, - /// Imported Markdown content to append to Worker history on explicit activation. - pub body: String, -} - #[derive(Debug, thiserror::Error)] pub enum SkillClientError { #[error("workspace client is unavailable: {0}")] diff --git a/crates/worker/src/worker.rs b/crates/worker/src/worker.rs index 3ec8a9a0a..da0471de8 100644 --- a/crates/worker/src/worker.rs +++ b/crates/worker/src/worker.rs @@ -3889,8 +3889,8 @@ impl Worker { .ok_or_else(|| { WorkerError::FlowInput("Workspace client has no Workspace scope".to_string()) })?; - let request = flow::FlowSourceResolveRequest { - selector: selector.clone(), + let request = server_api::FlowSourceResolveRequest { + selector: selector.to_string(), }; let response = workspace .execute(WorkspaceRequest { @@ -3915,13 +3915,19 @@ impl Worker { response.status ))); } - let source: flow::ResolvedFlowSource = serde_json::from_str(&response.body) + let wire_source: server_api::ResolvedFlowSource = serde_json::from_str(&response.body) .map_err(|error| WorkerError::FlowInput(format!("decode Flow source: {error}")))?; - if source.workspace_id != workspace_id || source.selector != selector { + if wire_source.workspace_id != workspace_id || wire_source.selector != selector.to_string() + { return Err(WorkerError::FlowInput( "Workspace returned a Flow source outside the requested scope".to_string(), )); } + let source: flow::ResolvedFlowSource = + serde_json::from_value(serde_json::to_value(wire_source).map_err(|error| { + WorkerError::FlowInput(format!("project Flow source transport: {error}")) + })?) + .map_err(|error| WorkerError::FlowInput(format!("convert Flow source: {error}")))?; let (state, initial_instructions) = flow::FlowRuntimeState::start(&source, uuid::Uuid::now_v7().to_string()) .map_err(|error| WorkerError::FlowInput(error.to_string()))?; diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 0f203c680..dbcf5f3c3 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -26,6 +26,7 @@ use memory::backend::{ }; use protocol::Segment; use protocol::stream::{decode_method, encode_event}; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use server_api::{ ActorAuthMethod, AuthBootstrapUserRequest, AuthPublicConfig, AuthUserResponse, @@ -3729,6 +3730,84 @@ fn generated_workspace_catalog_contract_router(service: ServerApiContractService fn generated_workspace_contract_router(service: ServerApiContractService) -> Router { let service = Arc::new(service); Router::new() + .merge(server_api::server_api_axum::workspace_current( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_scoped( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_metadata_settings( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_metadata_settings_update(service.clone())) + .merge(server_api::server_api_axum::workspace_signing_identity( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_signing_identity_provision(service.clone())) + .merge(server_api::server_api_axum::workspace_memory_settings( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_memory_settings_update(service.clone())) + .merge(server_api::server_api_axum::repository_access_projection( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_credential_list( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_credential_create(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_credential_generate(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_credential_get( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_credential_delete(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_credential_public_key(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_credential_rotate(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_host_trust_list( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_host_trust_put( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_host_trust_get( + service.clone(), + )) + .merge(server_api::server_api_axum::repository_ssh_host_trust_delete(service.clone())) + .merge(server_api::server_api_axum::workspace_config_tree( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_prompt_projection( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_config_tree_commit( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_config_revision( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_config_entry( + service.clone(), + )) + .merge(server_api::server_api_axum::profile_settings( + service.clone(), + )) + .merge(server_api::server_api_axum::flow_list(service.clone())) + .merge(server_api::server_api_axum::flow_put(service.clone())) + .merge(server_api::server_api_axum::flow_resolve(service.clone())) + .merge(server_api::server_api_axum::flow_get(service.clone())) + .merge(server_api::server_api_axum::memory_document( + service.clone(), + )) + .merge(server_api::server_api_axum::memory_staging_list( + service.clone(), + )) + .merge(server_api::server_api_axum::memory_backend(service.clone())) + .merge(server_api::server_api_axum::memory_consolidation( + service.clone(), + )) + .merge(server_api::server_api_axum::skill_list(service.clone())) + .merge(server_api::server_api_axum::skill_lint(service.clone())) + .merge(server_api::server_api_axum::skill_get(service.clone())) + .merge(server_api::server_api_axum::skill_activate(service.clone())) .merge(server_api::server_api_axum::worker_session(service.clone())) .merge(server_api::server_api_axum::repository_list( service.clone(), @@ -3742,7 +3821,19 @@ fn generated_workspace_contract_router(service: ServerApiContractService) -> Rou .merge(server_api::server_api_axum::repository_detail_alias( service.clone(), )) - .merge(server_api::server_api_axum::repository_create(service)) + .merge(server_api::server_api_axum::repository_create( + service.clone(), + )) + .layer(middleware::from_fn_with_state( + service.as_ref().clone(), + attach_server_request_context, + )) +} + +fn project_server_dto(value: &T) -> Result { + let value = serde_json::to_value(value) + .map_err(|error| Error::RegistryInconsistency(error.to_string()))?; + serde_json::from_value(value).map_err(|error| Error::RegistryInconsistency(error.to_string())) } impl server_api::ServerApi for ServerApiContractService { @@ -4001,6 +4092,667 @@ impl server_api::ServerApi for ServerApiContractService { }) } + async fn workspace_current( + &self, + context: server_api::ServerRequestContext, + ) -> std::result::Result { + workspace_response(self.workspace_api()?, context.actor) + .await + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_scoped( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + ) -> std::result::Result { + let api = self.workspace_api()?; + validate_workspace_scope(api, &workspace_id) + .map_err(ApiError::into_repository_api_error)?; + workspace_response(api, context.actor) + .await + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_metadata_settings( + &self, + workspace_id: String, + ) -> std::result::Result + { + scoped_get_workspace_settings( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_metadata_settings_update( + &self, + workspace_id: String, + request: UpdateWorkspaceMetadataRequest, + ) -> std::result::Result + { + scoped_update_workspace_settings( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_signing_identity( + &self, + actor: RequestActor, + workspace_id: String, + ) -> std::result::Result { + scoped_get_workspace_signing_identity( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_signing_identity_provision( + &self, + actor: RequestActor, + workspace_id: String, + ) -> std::result::Result { + scoped_provision_workspace_signing_identity( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_memory_settings( + &self, + workspace_id: String, + ) -> std::result::Result + { + scoped_get_workspace_memory_settings( + State(self.workspace_api()?.clone()), + AxumPath(workspace_id), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_memory_settings_update( + &self, + workspace_id: String, + request: server_api::UpdateWorkspaceMemorySettingsRequest, + ) -> std::result::Result + { + scoped_update_workspace_memory_settings( + State(self.workspace_api()?.clone()), + AxumPath(workspace_id), + Json(request), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_access_projection( + &self, + actor: RequestActor, + workspace_id: String, + ) -> std::result::Result { + scoped_get_repository_access_projection( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_list( + &self, + actor: RequestActor, + workspace_id: String, + ) -> std::result::Result< + server_api::RepositorySshCredentialListResponse, + server_api::RepositoryApiError, + > { + scoped_list_repository_ssh_credentials( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + ) + .await + .map(|Json(response)| server_api::RepositorySshCredentialListResponse(response)) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_create( + &self, + actor: RequestActor, + workspace_id: String, + request: CreateRepositorySshCredentialRequest, + ) -> std::result::Result { + scoped_create_repository_ssh_credential( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + Json(request), + ) + .await + .map(|(_status, Json(response))| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_generate( + &self, + actor: RequestActor, + workspace_id: String, + request: GenerateRepositorySshCredentialRequest, + ) -> std::result::Result { + scoped_generate_repository_ssh_credential( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + Json(request), + ) + .await + .map(|(_status, Json(response))| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_get( + &self, + actor: RequestActor, + workspace_id: String, + credential_id: String, + ) -> std::result::Result { + scoped_get_repository_ssh_credential( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryCredentialPath { + workspace_id, + credential_id, + }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_delete( + &self, + actor: RequestActor, + workspace_id: String, + credential_id: String, + request: DeleteRepositorySshCredentialRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_delete_repository_ssh_credential( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryCredentialPath { + workspace_id, + credential_id, + }), + Extension(actor), + Json(request), + ) + .await + .map(|_| ()) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_public_key( + &self, + actor: RequestActor, + workspace_id: String, + credential_id: String, + ) -> std::result::Result { + scoped_get_repository_ssh_public_key( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryCredentialPath { + workspace_id, + credential_id, + }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_credential_rotate( + &self, + actor: RequestActor, + workspace_id: String, + credential_id: String, + request: RotateRepositorySshCredentialRequest, + ) -> std::result::Result { + scoped_rotate_repository_ssh_credential( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryCredentialPath { + workspace_id, + credential_id, + }), + Extension(actor), + Json(request), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_host_trust_list( + &self, + actor: RequestActor, + workspace_id: String, + ) -> std::result::Result< + server_api::RepositorySshHostTrustListResponse, + server_api::RepositoryApiError, + > { + scoped_list_repository_ssh_host_trusts( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + ) + .await + .map(|Json(response)| server_api::RepositorySshHostTrustListResponse(response)) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_host_trust_put( + &self, + actor: RequestActor, + workspace_id: String, + request: PutRepositorySshHostTrustRequest, + ) -> std::result::Result< + server_api::RepositorySshHostTrustMutationResponse, + server_api::RepositoryApiError, + > { + let created = request.expected_revision.is_none(); + scoped_put_repository_ssh_host_trust( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + Json(request), + ) + .await + .map( + |(_status, Json(host_trust))| server_api::RepositorySshHostTrustMutationResponse { + host_trust, + created, + }, + ) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_host_trust_get( + &self, + actor: RequestActor, + workspace_id: String, + host_trust_id: String, + ) -> std::result::Result { + scoped_get_repository_ssh_host_trust( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryHostTrustPath { + workspace_id, + host_trust_id, + }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn repository_ssh_host_trust_delete( + &self, + actor: RequestActor, + workspace_id: String, + host_trust_id: String, + request: DeleteRepositorySshHostTrustRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_delete_repository_ssh_host_trust( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryHostTrustPath { + workspace_id, + host_trust_id, + }), + Extension(actor), + Json(request), + ) + .await + .map(|_| ()) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_config_tree( + &self, + workspace_id: String, + ) -> std::result::Result + { + scoped_get_workspace_config_tree( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_prompt_projection( + &self, + workspace_id: String, + ) -> std::result::Result + { + scoped_get_prompt_projection( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_config_tree_commit( + &self, + workspace_id: String, + request: server_api::ConfigCommitRequest, + ) -> std::result::Result + { + let request = project_server_dto(&request) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + scoped_commit_workspace_config_tree( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map(|(_status, Json(response))| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_config_revision( + &self, + workspace_id: String, + revision: String, + ) -> std::result::Result { + let revision = revision.parse::().map_err(|_| { + ApiError::from(Error::InvalidInput( + "config revision must be an unsigned integer".into(), + )) + .into_repository_api_error() + })?; + scoped_get_workspace_config_revision( + State(self.workspace_api()?.clone()), + AxumPath(WorkspaceConfigRevisionPath { + workspace_id, + revision, + }), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn workspace_config_entry( + &self, + workspace_id: String, + path: String, + ) -> std::result::Result { + scoped_get_workspace_config_entry( + State(self.workspace_api()?.clone()), + AxumPath(WorkspaceConfigEntryPath { workspace_id, path }), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn profile_settings( + &self, + workspace_id: String, + ) -> std::result::Result { + scoped_get_profile_settings( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn flow_list( + &self, + workspace_id: String, + ) -> std::result::Result + { + scoped_list_flows( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| server_api::FlowSourceListResponse(response)) + .map_err(ApiError::into_repository_api_error) + } + + async fn flow_put( + &self, + workspace_id: String, + request: server_api::PutFlowRequest, + ) -> std::result::Result { + scoped_put_flow( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn flow_resolve( + &self, + workspace_id: String, + request: server_api::FlowSourceResolveRequest, + ) -> std::result::Result { + let request = project_server_dto(&request) + .map_err(|error| ApiError::from(error).into_repository_api_error())?; + scoped_resolve_flow_source( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn flow_get( + &self, + workspace_id: String, + flow_id: String, + ) -> std::result::Result { + scoped_get_flow( + State(self.workspace_api()?.clone()), + AxumPath(ScopedFlowPath { + workspace_id, + flow_id, + }), + ) + .await + .and_then(|Json(response)| { + project_server_dto(&response) + .map(Json) + .map_err(ApiError::from) + }) + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn memory_document( + &self, + workspace_id: String, + ) -> std::result::Result { + scoped_get_memory_document( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn memory_staging_list( + &self, + workspace_id: String, + query: server_api::MemoryStagingQuery, + ) -> std::result::Result { + scoped_list_memory_staging( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Query(MemoryStagingQuery { + limit: query.limit.map(|limit| limit as usize), + }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn memory_backend( + &self, + workspace_id: String, + request: server_api::MemoryBackendRequest, + ) -> std::result::Result + { + scoped_memory_backend_operation( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request.0), + ) + .await + .map(|Json(response)| server_api::MemoryBackendResponse(response)) + .map_err(ApiError::into_repository_api_error) + } + + async fn memory_consolidation( + &self, + workspace_id: String, + request: server_api::MemoryConsolidateStagingRequest, + ) -> std::result::Result + { + scoped_memory_consolidation( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(MemoryConsolidateStagingOperation { + force: request.force, + }), + ) + .await + .map(|Json(response)| server_api::MemoryConsolidationResponse { + status: response.status, + summary: response.summary, + candidate_count: response.candidate_count, + total_bytes: response.total_bytes, + }) + .map_err(ApiError::into_repository_api_error) + } + + async fn skill_list( + &self, + workspace_id: String, + ) -> std::result::Result { + scoped_list_skills( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn skill_lint( + &self, + workspace_id: String, + ) -> std::result::Result { + scoped_lint_skills( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn skill_get( + &self, + workspace_id: String, + name: String, + ) -> std::result::Result { + scoped_get_skill( + State(self.workspace_api()?.clone()), + AxumPath(ScopedSkillPath { workspace_id, name }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn skill_activate( + &self, + workspace_id: String, + name: String, + ) -> std::result::Result + { + scoped_activate_skill( + State(self.workspace_api()?.clone()), + AxumPath(ScopedSkillPath { workspace_id, name }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + async fn worker_session( &self, workspace_id: String, @@ -4125,97 +4877,6 @@ fn build_inner_router(api: WorkspaceApi) -> Router { let scoped_ticket_orchestration_plans_query_path = format!("/api/w/{{workspace_id}}{TICKET_ORCHESTRATION_PLANS_QUERY_PATH}"); let workspace = Router::new() - .route("/api/workspace", get(get_workspace)) - .route("/api/w/{workspace_id}/workspace", get(scoped_get_workspace)) - .route( - "/api/w/{workspace_id}/settings", - get(scoped_get_workspace_settings).put(scoped_update_workspace_settings), - ) - .route( - "/api/w/{workspace_id}/settings/signing-identity", - get(scoped_get_workspace_signing_identity), - ) - .route( - "/api/w/{workspace_id}/settings/signing-identity/provision", - post(scoped_provision_workspace_signing_identity), - ) - .route( - "/api/w/{workspace_id}/settings/memory", - get(scoped_get_workspace_memory_settings) - .put(scoped_update_workspace_memory_settings), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access", - get(scoped_get_repository_access_projection), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/credentials", - get(scoped_list_repository_ssh_credentials) - .post(scoped_create_repository_ssh_credential), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/credentials/generate", - post(scoped_generate_repository_ssh_credential), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}", - get(scoped_get_repository_ssh_credential) - .delete(scoped_delete_repository_ssh_credential), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/public-key", - get(scoped_get_repository_ssh_public_key), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/rotate", - post(scoped_rotate_repository_ssh_credential), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/host-trusts", - get(scoped_list_repository_ssh_host_trusts) - .post(scoped_put_repository_ssh_host_trust), - ) - .route( - "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}", - get(scoped_get_repository_ssh_host_trust) - .delete(scoped_delete_repository_ssh_host_trust), - ) - .route( - "/api/w/{workspace_id}/config/source-tree", - get(scoped_get_workspace_config_tree), - ) - .route( - "/api/w/{workspace_id}/config/projections/prompts", - get(scoped_get_prompt_projection), - ) - .route( - "/api/w/{workspace_id}/config/source-tree/commit", - post(scoped_commit_workspace_config_tree), - ) - .route( - "/api/w/{workspace_id}/config/source-tree/revisions/{revision}", - get(scoped_get_workspace_config_revision), - ) - .route( - "/api/w/{workspace_id}/config/source-tree/entries/{*path}", - get(scoped_get_workspace_config_entry), - ) - .route( - "/api/w/{workspace_id}/settings/profiles", - get(scoped_get_profile_settings), - ) - .route( - "/api/w/{workspace_id}/flows", - get(scoped_list_flows).put(scoped_put_flow), - ) - .route( - "/api/w/{workspace_id}/flows/resolve", - post(scoped_resolve_flow_source), - ) - .route( - "/api/w/{workspace_id}/flows/{flow_id}", - get(scoped_get_flow), - ) .route("/api/tickets", get(list_tickets)) .route( "/api/w/{workspace_id}/tickets", @@ -4225,30 +4886,7 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/api/w/{workspace_id}/tickets/query", post(scoped_query_tickets), ) - .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/default-intake-ready-body", post(scoped_default_intake_ready_body), @@ -5092,13 +5730,6 @@ struct ScopedFlowPath { flow_id: String, } -#[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] -struct PutFlowRequest { - path: String, - content: String, -} - #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct AttachCurrentWorkerWorkdirRequest { @@ -5209,7 +5840,7 @@ async fn scoped_list_flows( async fn scoped_put_flow( State(api): State, AxumPath(path): AxumPath, - Json(request): Json, + Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let definition = flow::compile_flow_source(&request.content).map_err(|error| { @@ -5299,15 +5930,6 @@ async fn scoped_get_flow( Ok(Json(source)) } -async fn scoped_get_workspace( - headers: HeaderMap, - State(api): State, - AxumPath(path): AxumPath, -) -> ApiResult> { - validate_workspace_scope(&api, &path.workspace_id)?; - get_workspace(headers, State(api)).await -} - async fn scoped_get_workspace_settings( State(api): State, AxumPath(path): AxumPath, @@ -5508,13 +6130,6 @@ async fn scoped_get_prompt_projection( Ok(Json(projection.as_ref().clone())) } -#[derive(Debug, Serialize)] -struct WorkspaceConfigTreeResponse { - snapshot: ConfigTreeSnapshot, - contract: config_source::ToolchainContract, - projection_digest: String, -} - async fn scoped_get_workspace_memory_settings( State(api): State, AxumPath(workspace_id): AxumPath, @@ -5798,7 +6413,7 @@ async fn scoped_delete_repository_ssh_host_trust( async fn scoped_get_workspace_config_tree( State(api): State, AxumPath(path): AxumPath, -) -> ApiResult> { +) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let state = api .config_store @@ -5812,11 +6427,7 @@ async fn scoped_get_workspace_config_tree( ), projection_digest: config_source::digest_bytes(b"[]"), }); - Ok(Json(WorkspaceConfigTreeResponse { - snapshot: state.snapshot, - contract: state.contract, - projection_digest: state.projection_digest, - })) + Ok(Json(project_server_dto(&state)?)) } async fn scoped_get_workspace_config_entry( @@ -5844,7 +6455,7 @@ async fn scoped_commit_workspace_config_tree( State(api): State, AxumPath(path): AxumPath, Json(request): Json, -) -> ApiResult<(StatusCode, Json)> { +) -> ApiResult<(StatusCode, Json)> { validate_workspace_scope(&api, &path.workspace_id)?; let candidate = api .config_store @@ -5871,14 +6482,7 @@ async fn scoped_commit_workspace_config_tree( .runtime .observe_workspace_prompt_projection((*projection).clone()); } - Ok(( - StatusCode::CREATED, - Json(WorkspaceConfigTreeResponse { - snapshot: state.snapshot, - contract: state.contract, - projection_digest: state.projection_digest, - }), - )) + Ok((StatusCode::CREATED, Json(project_server_dto(&state)?))) } async fn scoped_get_profile_settings( @@ -15915,12 +16519,10 @@ async fn require_actor(api: &ServerAuthApi, headers: &HeaderMap) -> ApiResult, -) -> ApiResult> { - let cookie_name = auth_public_config(&api.config).cookie_name; - let actor = resolve_request_actor(api.store.as_ref(), &headers, &cookie_name).await?; +async fn workspace_response( + api: &WorkspaceApi, + actor: Option, +) -> ApiResult { let schema_version = api.store.schema_version().await?; let stored = api .store @@ -15932,7 +16534,7 @@ async fn get_workspace( .is_some_and(|actor| stored.owner_account_id == actor.account_id); let companion_status = api.companion.status(); let companion_console = companion_console_extension_point(&companion_status); - Ok(Json(WorkspaceResponse { + Ok(WorkspaceResponse { workspace_id: stored.workspace_id, display_name: stored.display_name, record_authority: "server_db".to_string(), @@ -15958,7 +16560,7 @@ async fn get_workspace( }, companion_console, }, - })) + }) } fn companion_console_extension_point( @@ -22570,10 +23172,10 @@ mod tests { #[test] fn workspace_deletion_execution_is_server_owned_and_polling_is_read_only() { let source = include_str!("server.rs"); - let start = handler_source(source, "start_server_workspace_deletion"); + let start = handler_source(source, "workspace_deletion_start"); assert!(start.contains("schedule_workspace_deletion")); assert!(!start.contains("execute_workspace_deletion(&request")); - let poll = handler_source(source, "get_server_workspace_deletion"); + let poll = handler_source(source, "workspace_deletion_get"); assert!(!poll.contains("execute_workspace_deletion")); assert!(source.contains("api.recover_workspace_deletions().await?")); } @@ -22611,9 +23213,10 @@ mod tests { .find(&format!("async fn {name}")) .unwrap_or_else(|| panic!("missing handler {name}")); let tail = &source[start..]; - let end = tail[1..] - .find("\nasync fn ") - .map(|offset| offset + 1) + let end = ["\nasync fn ", "\n async fn "] + .into_iter() + .filter_map(|prefix| tail[1..].find(prefix).map(|offset| offset + 1)) + .min() .unwrap_or(tail.len()); &tail[..end] } @@ -37192,6 +37795,55 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3); assert_eq!(shown["linked_tickets"], json!(["00000000001J3"])); } + #[tokio::test] + async fn generated_config_source_routes_preserve_nested_entry_paths() { + let dir = tempfile::tempdir().unwrap(); + let app = test_app(dir.path()).await; + let source_tree_path = format!("/api/w/{TEST_WORKSPACE_ID}/config/source-tree"); + let initial = get_json(app.clone(), &source_tree_path).await; + let revision = initial["snapshot"]["revision"].as_u64().unwrap(); + let digest = initial["snapshot"]["digest"].as_str().unwrap(); + + let committed = request_json( + app.clone(), + "POST", + &format!("{source_tree_path}/commit"), + Some(json!({ + "base_revision": revision, + "base_digest": digest, + "changes": [{ + "kind": "create", + "path": "notes/readme.txt", + "content_type": "text", + "content": "nested entry" + }], + "entrypoints": ["main.dcdl"] + })), + StatusCode::CREATED, + ) + .await; + let committed_revision = committed["snapshot"]["revision"].as_u64().unwrap(); + + let revision_snapshot = get_json( + app.clone(), + &format!("{source_tree_path}/revisions/{committed_revision}"), + ) + .await; + assert_eq!(revision_snapshot["revision"], committed_revision); + assert_eq!( + revision_snapshot["entries"]["notes/readme.txt"]["content"], + "nested entry" + ); + + let entry = get_json( + app, + &format!("{source_tree_path}/entries/notes%2Freadme.txt"), + ) + .await; + assert_eq!(entry["path"], "notes/readme.txt"); + assert_eq!(entry["content"], "nested entry"); + } + #[tokio::test] async fn profile_settings_are_read_only_virtual_config_projection() { let dir = tempfile::tempdir().unwrap(); diff --git a/openapi/server-api.json b/openapi/server-api.json index cfc6c7ef9..8b60d309c 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -9,6 +9,108 @@ ], "type": "string" }, + "Array_of_FlowSourceRecord": { + "items": { + "$ref": "#/components/schemas/FlowSourceRecord" + }, + "type": "array" + }, + "Array_of_RepositorySshCredential": { + "items": { + "$ref": "#/components/schemas/RepositorySshCredential" + }, + "type": "array" + }, + "Array_of_RepositorySshHostTrust": { + "items": { + "$ref": "#/components/schemas/RepositorySshHostTrust" + }, + "type": "array" + }, + "AuditEvent": { + "oneOf": [ + { + "$ref": "#/components/schemas/WorkerLifecycleAudit", + "properties": { + "event": { + "const": "worker_lifecycle", + "type": "string" + } + }, + "required": [ + "event" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/RecordOperationAudit", + "properties": { + "event": { + "const": "record_operation", + "type": "string" + } + }, + "required": [ + "event" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/RecordUsageAudit", + "properties": { + "event": { + "const": "record_usage", + "type": "string" + } + }, + "required": [ + "event" + ], + "type": "object" + } + ], + "properties": { + "id": { + "type": "string" + }, + "occurred_at": { + "type": "string" + } + }, + "required": [ + "id", + "occurred_at" + ], + "type": "object" + }, + "AuditStatus": { + "enum": [ + "success", + "failed", + "skipped" + ], + "type": "string" + }, + "AuditTrigger": { + "enum": [ + "session_end", + "turn_threshold", + "token_threshold", + "staging_backlog", + "idle", + "manual", + "startup_recovery", + "unknown" + ], + "type": "string" + }, + "AuditWorker": { + "enum": [ + "memory_extract", + "memory_consolidation" + ], + "type": "string" + }, "AuthBootstrapUserRequest": { "additionalProperties": false, "properties": { @@ -89,6 +191,430 @@ ], "type": "object" }, + "CandidateKind": { + "description": "Candidate kinds that extract is allowed to stage.", + "enum": [ + "preference", + "working_assumption", + "constraint", + "decision", + "open_question", + "lesson" + ], + "type": "string" + }, + "CompiledFlowDefinition": { + "additionalProperties": false, + "properties": { + "content_digest": { + "type": "string" + }, + "initial": { + "type": "string" + }, + "name": { + "type": "string" + }, + "schema_version": { + "format": "uint32", + "minimum": 0, + "type": "integer" + }, + "states": { + "additionalProperties": { + "$ref": "#/components/schemas/CompiledFlowState" + }, + "type": "object" + } + }, + "required": [ + "schema_version", + "name", + "initial", + "states", + "content_digest" + ], + "type": "object" + }, + "CompiledFlowState": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "terminal": { + "type": "boolean" + }, + "transitions": { + "items": { + "$ref": "#/components/schemas/CompiledFlowTransition" + }, + "type": "array" + } + }, + "required": [ + "id", + "instructions", + "terminal", + "transitions" + ], + "type": "object" + }, + "CompiledFlowTransition": { + "additionalProperties": false, + "properties": { + "condition": { + "type": "string" + }, + "id": { + "type": "string" + }, + "synthetic": { + "type": "boolean" + }, + "target": { + "type": "string" + } + }, + "required": [ + "id", + "target", + "condition", + "synthetic" + ], + "type": "object" + }, + "ConfigCommitRequest": { + "additionalProperties": false, + "description": "Compare-and-swap request for applying a complete set of source-tree changes.", + "properties": { + "base_digest": { + "type": "string" + }, + "base_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "changes": { + "items": { + "$ref": "#/components/schemas/ConfigTreeChange" + }, + "type": "array" + }, + "entrypoints": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "base_revision", + "base_digest", + "changes", + "entrypoints" + ], + "type": "object" + }, + "ConfigContentType": { + "description": "Source content kinds accepted by the Workspace configuration tree.", + "enum": [ + "decodal", + "text" + ], + "type": "string" + }, + "ConfigEntry": { + "additionalProperties": false, + "description": "One source entry in the Workspace configuration tree.", + "properties": { + "content": { + "type": "string" + }, + "content_digest": { + "type": "string" + }, + "content_type": { + "$ref": "#/components/schemas/ConfigContentType" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path", + "content_type", + "content", + "content_digest" + ], + "type": "object" + }, + "ConfigProjectionValidator": { + "oneOf": [ + { + "properties": { + "key_aliases": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "kind": { + "const": "static_template_catalog", + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "kind", + "namespace" + ], + "type": "object" + } + ] + }, + "ConfigSchemaContribution": { + "additionalProperties": false, + "properties": { + "namespace": { + "type": "string" + }, + "projection_validator": { + "anyOf": [ + { + "$ref": "#/components/schemas/ConfigProjectionValidator" + }, + { + "type": "null" + } + ] + }, + "provider_id": { + "type": "string" + }, + "source": { + "type": "string" + }, + "source_digest": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "provider_id", + "namespace", + "version", + "source", + "source_digest" + ], + "type": "object" + }, + "ConfigTreeChange": { + "description": "Base-relative change applied by a Workspace configuration commit.", + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "content_type": { + "$ref": "#/components/schemas/ConfigContentType" + }, + "kind": { + "const": "create", + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "kind", + "path", + "content_type", + "content" + ], + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "expected_digest": { + "type": "string" + }, + "kind": { + "const": "update", + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "kind", + "path", + "expected_digest", + "content" + ], + "type": "object" + }, + { + "properties": { + "expected_digest": { + "type": "string" + }, + "from": { + "type": "string" + }, + "kind": { + "const": "rename", + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "kind", + "from", + "to", + "expected_digest" + ], + "type": "object" + }, + { + "properties": { + "expected_digest": { + "type": "string" + }, + "kind": { + "const": "delete", + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "kind", + "path", + "expected_digest" + ], + "type": "object" + } + ] + }, + "ConfigTreeSnapshot": { + "additionalProperties": false, + "description": "Immutable Workspace configuration tree snapshot.", + "properties": { + "digest": { + "type": "string" + }, + "entries": { + "additionalProperties": { + "$ref": "#/components/schemas/ConfigEntry" + }, + "type": "object" + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "revision", + "digest", + "entries" + ], + "type": "object" + }, + "ConsolidationAudit": { + "properties": { + "consumed_staging_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "invalid_staging_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "operations": { + "$ref": "#/components/schemas/OperationCounts", + "default": { + "delete": 0, + "drop": 0, + "edit": 0, + "merge": 0, + "trim": 0, + "write": 0 + } + }, + "staging_bytes": { + "default": 0, + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "staging_count": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "type": "object" + }, + "CreateRepositorySshCredentialRequest": { + "additionalProperties": false, + "properties": { + "credential_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operation_id": { + "type": "string" + }, + "passphrase": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "private_key": { + "type": "string" + } + }, + "required": [ + "operation_id", + "credential_id", + "name", + "private_key" + ], + "type": "object" + }, "CreateWorkspaceRepositoryRequest": { "additionalProperties": false, "description": "Browser/user intent for registering one Repository in a Workspace.\n\nThe Server parses and canonicalizes `source`; callers cannot assert a\ntransport classification or supply credential material through this DTO.", @@ -132,6 +658,44 @@ ], "type": "object" }, + "DeleteRepositorySshCredentialRequest": { + "additionalProperties": false, + "properties": { + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "operation_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "expected_revision" + ], + "type": "object" + }, + "DeleteRepositorySshHostTrustRequest": { + "additionalProperties": false, + "properties": { + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "operation_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "expected_revision" + ], + "type": "object" + }, "DeviceAccessTokenType": { "enum": [ "Bearer" @@ -300,6 +864,317 @@ ], "type": "string" }, + "EffectivePromptCatalog": { + "additionalProperties": false, + "properties": { + "catalog_digest": { + "type": "string" + }, + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "schema_fingerprint": { + "type": "string" + }, + "source_digest": { + "type": "string" + }, + "templates": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "toolchain_fingerprint": { + "type": "string" + } + }, + "required": [ + "templates", + "config_revision", + "schema_fingerprint", + "toolchain_fingerprint", + "catalog_digest" + ], + "type": "object" + }, + "EvidenceKind": { + "description": "Extensible evidence kind tag used by staging source anchors.\n\nKnown values include `message`, `tool_call`, `tool_result`, `file_ref`,\n`ticket_ref`, and `objective_ref`, but callers may use newer bounded tags\nwithout changing the schema.", + "type": "string" + }, + "EvidenceOrigin": { + "additionalProperties": false, + "description": "Bounded origin snapshot attached to extraction evidence. This is audit\nmetadata only and cannot authorize Workspace operations.", + "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, + "flow_definition_id": { + "type": [ + "string", + "null" + ] + }, + "flow_definition_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "flow_selector": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "$ref": "#/components/schemas/EvidenceOriginKind" + }, + "runtime_id": { + "type": [ + "string", + "null" + ] + }, + "worker_id": { + "type": [ + "string", + "null" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "EvidenceOriginKind": { + "enum": [ + "human_input", + "worker_input", + "flow_instruction", + "backend_instruction", + "model_output", + "tool_output", + "derived_summary", + "legacy_unknown" + ], + "type": "string" + }, + "ExtractAudit": { + "properties": { + "entry_range": { + "items": { + "$ref": "#/components/schemas/MemoryJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "history_range": { + "items": { + "$ref": "#/components/schemas/MemoryJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "segment_id": { + "type": [ + "string", + "null" + ] + }, + "session_id": { + "type": [ + "string", + "null" + ] + }, + "staging_count": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "staging_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "staging_paths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "ExtractedCandidate": { + "description": "Model-submitted candidate before host-side staging metadata is attached.", + "properties": { + "claim": { + "description": "Concise candidate claim.", + "type": "string" + }, + "evidence_ids": { + "description": "Evidence ids selected by the extract worker. The transitional\n`write_extracted` path may leave this empty until `session-explore`\nprovides host-issued evidence ids.", + "items": { + "type": "string" + }, + "type": "array" + }, + "kind": { + "$ref": "#/components/schemas/CandidateKind", + "description": "Kind of candidate. This is intentionally a narrow taxonomy, not an\nactivity-log category." + }, + "staleness": { + "description": "Optional invalidation / staleness hint.", + "type": [ + "string", + "null" + ] + }, + "why_useful": { + "description": "Why this may be useful for future work or consolidation.", + "type": "string" + } + }, + "required": [ + "kind", + "claim", + "why_useful" + ], + "type": "object" + }, + "ExtractedPayload": { + "description": "Transitional extract output: zero or more flat candidates.", + "properties": { + "candidates": { + "default": [], + "items": { + "$ref": "#/components/schemas/ExtractedCandidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "FlowSourceKind": { + "enum": [ + "builtin", + "workspace" + ], + "type": "string" + }, + "FlowSourceRecord": { + "additionalProperties": false, + "description": "Current source record for one Workspace-authored Flow.", + "properties": { + "content": { + "type": "string" + }, + "content_digest": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "flow_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source_kind": { + "$ref": "#/components/schemas/FlowSourceKind" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "flow_id", + "source_kind", + "name", + "path", + "content", + "content_digest", + "revision", + "created_at", + "updated_at" + ], + "type": "object" + }, + "FlowSourceResolveRequest": { + "additionalProperties": false, + "properties": { + "selector": { + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "GenerateRepositorySshCredentialRequest": { + "additionalProperties": false, + "properties": { + "credential_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operation_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "credential_id", + "name" + ], + "type": "object" + }, "GitRemoteSummary": { "additionalProperties": false, "properties": { @@ -387,6 +1262,1019 @@ ], "type": "object" }, + "MemoryAppendAuditOperation": { + "properties": { + "event": { + "$ref": "#/components/schemas/AuditEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + }, + "MemoryBackendAckOutput": { + "properties": { + "summary": { + "type": "string" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + "MemoryBackendHttpResponse": { + "oneOf": [ + { + "properties": { + "result": { + "$ref": "#/components/schemas/MemoryBackendOperationResult" + }, + "status": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "status", + "result" + ], + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "status": { + "const": "error", + "type": "string" + } + }, + "required": [ + "status", + "message" + ], + "type": "object" + } + ] + }, + "MemoryBackendOperation": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemoryQueryOperation", + "properties": { + "operation": { + "const": "query", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryDocumentReadOperation", + "properties": { + "operation": { + "const": "read_document", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryDocumentUpdateOperation", + "properties": { + "operation": { + "const": "update_document", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryResidentSummaryOperation", + "properties": { + "operation": { + "const": "resident_summary", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryAppendAuditOperation", + "properties": { + "operation": { + "const": "append_audit", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStageCandidateOperation", + "properties": { + "operation": { + "const": "stage_candidate", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStageExtractedOperation", + "properties": { + "operation": { + "const": "stage_extracted", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStagingListOperation", + "properties": { + "operation": { + "const": "staging_list", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStagingReadOperation", + "properties": { + "operation": { + "const": "staging_read", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStagingCloseOperation", + "properties": { + "operation": { + "const": "staging_close", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + } + ] + }, + "MemoryBackendOperationResult": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemoryToolOutput", + "properties": { + "kind": { + "const": "tool_output", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryBackendAckOutput", + "properties": { + "kind": { + "const": "acknowledged", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "$ref": "#/components/schemas/MemoryStagingWriteOutput", + "properties": { + "kind": { + "const": "staging_written", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + } + ] + }, + "MemoryBackendRequest": { + "$ref": "#/components/schemas/MemoryBackendOperation", + "description": "Public wrapper retaining the established tagged Memory backend wire shape." + }, + "MemoryBackendResponse": { + "$ref": "#/components/schemas/MemoryBackendHttpResponse", + "description": "Public wrapper retaining the established tagged Memory backend response shape." + }, + "MemoryCandidateKind": { + "description": "Candidate kinds exposed by the Memory staging resource.", + "enum": [ + "preference", + "working_assumption", + "constraint", + "decision", + "open_question", + "lesson" + ], + "type": "string" + }, + "MemoryConsolidateStagingRequest": { + "additionalProperties": false, + "properties": { + "force": { + "default": false, + "type": "boolean" + } + }, + "type": "object" + }, + "MemoryConsolidationResponse": { + "additionalProperties": false, + "properties": { + "candidate_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "status": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "total_bytes": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "status", + "summary", + "candidate_count", + "total_bytes" + ], + "type": "object" + }, + "MemoryDocumentReadOperation": { + "properties": { + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "offset": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "MemoryDocumentResponse": { + "additionalProperties": false, + "description": "Public Workspace Memory document projection.", + "properties": { + "body_md": { + "type": "string" + }, + "bytes": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "record_source": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "body_md", + "created_at", + "updated_at", + "bytes", + "record_source" + ], + "type": "object" + }, + "MemoryDocumentUpdateOperation": { + "properties": { + "new_string": { + "type": "string" + }, + "old_string": { + "type": "string" + }, + "replace_all": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "old_string", + "new_string" + ], + "type": "object" + }, + "MemoryEvidenceOrigin": { + "additionalProperties": false, + "description": "Bounded origin metadata copied from one typed Memory evidence anchor.\n\nThis is provenance only. It carries no message body, prompt, reasoning,\nsecret, tool output, or authorization authority.", + "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, + "flow_definition_id": { + "type": [ + "string", + "null" + ] + }, + "flow_definition_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "flow_selector": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "$ref": "#/components/schemas/MemoryEvidenceOriginKind" + }, + "runtime_id": { + "type": [ + "string", + "null" + ] + }, + "worker_id": { + "type": [ + "string", + "null" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "MemoryEvidenceOriginKind": { + "description": "Typed, bounded provenance classification for public Memory evidence anchors.", + "enum": [ + "human_input", + "worker_input", + "flow_instruction", + "backend_instruction", + "model_output", + "tool_output", + "derived_summary", + "legacy_unknown" + ], + "type": "string" + }, + "MemoryJsonSafeU64": { + "description": "Schema-only representation for JSON integers that must remain exactly\nrepresentable by JavaScript consumers.", + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "MemoryQueryOperation": { + "properties": { + "query": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "MemoryResidentSummaryOperation": { + "type": "object" + }, + "MemorySettingsAudit": { + "properties": { + "language": { + "type": "string" + }, + "settings_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "settings_revision", + "language" + ], + "type": "object" + }, + "MemorySourceEvidenceRef": { + "additionalProperties": false, + "description": "Bounded source anchor included in one Memory staging record.", + "properties": { + "entry_range": { + "items": { + "$ref": "#/components/schemas/ServerJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "evidence_id": { + "type": [ + "string", + "null" + ] + }, + "evidence_kind": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/MemoryEvidenceOrigin" + }, + { + "type": "null" + } + ] + }, + "segment_id": { + "type": [ + "string", + "null" + ] + }, + "session_id": { + "type": [ + "string", + "null" + ] + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "MemorySourceRef": { + "additionalProperties": false, + "description": "Record-level source range for one Memory staging candidate.", + "properties": { + "range": { + "items": { + "$ref": "#/components/schemas/ServerJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "segment_id": { + "type": "string" + } + }, + "required": [ + "segment_id", + "range" + ], + "type": "object" + }, + "MemoryStageCandidateOperation": { + "properties": { + "candidate": { + "$ref": "#/components/schemas/ExtractedCandidate" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/StagingEvidence" + }, + "type": "array" + }, + "extract_run_id": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/SourceRef" + }, + "source_refs": { + "items": { + "$ref": "#/components/schemas/SourceEvidenceRef" + }, + "type": "array" + } + }, + "required": [ + "source", + "extract_run_id", + "candidate" + ], + "type": "object" + }, + "MemoryStageExtractedOperation": { + "properties": { + "payload": { + "$ref": "#/components/schemas/ExtractedPayload" + }, + "source": { + "$ref": "#/components/schemas/SourceRef" + } + }, + "required": [ + "source", + "payload" + ], + "type": "object" + }, + "MemoryStagingAffectedMemory": { + "properties": { + "operation": { + "$ref": "#/components/schemas/MemoryStagingAffectedMemoryOperation" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "MemoryStagingAffectedMemoryOperation": { + "enum": [ + "edit" + ], + "type": "string" + }, + "MemoryStagingCloseAction": { + "enum": [ + "applied", + "discarded", + "invalid", + "duplicate", + "already_covered" + ], + "type": "string" + }, + "MemoryStagingCloseOperation": { + "properties": { + "action": { + "$ref": "#/components/schemas/MemoryStagingCloseAction" + }, + "affected_memory": { + "items": { + "$ref": "#/components/schemas/MemoryStagingAffectedMemory" + }, + "type": "array" + }, + "candidate_id": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "candidate_id", + "action", + "reason" + ], + "type": "object" + }, + "MemoryStagingEntry": { + "additionalProperties": false, + "description": "Public list entry for one valid Memory staging record.", + "properties": { + "byte_len": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "id": { + "type": "string" + }, + "record": { + "$ref": "#/components/schemas/MemoryStagingRecord" + } + }, + "required": [ + "id", + "byte_len", + "record" + ], + "type": "object" + }, + "MemoryStagingEvidence": { + "additionalProperties": false, + "description": "Bounded evidence snippet included in one Memory staging record.", + "properties": { + "entry_range": { + "items": { + "$ref": "#/components/schemas/ServerJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "excerpt": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/MemoryEvidenceOrigin" + }, + { + "type": "null" + } + ] + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, + "MemoryStagingListOperation": { + "properties": { + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "MemoryStagingListResponse": { + "additionalProperties": false, + "description": "Public response returned by the Workspace Memory staging list resource.", + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "invalid_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "items": { + "items": { + "$ref": "#/components/schemas/MemoryStagingEntry" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "order": { + "type": "string" + }, + "record_authority": { + "type": "string" + }, + "returned_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "total_valid_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "limit", + "returned_count", + "total_valid_count", + "invalid_count", + "truncated", + "order", + "record_authority", + "items", + "diagnostics" + ], + "type": "object" + }, + "MemoryStagingQuery": { + "additionalProperties": false, + "description": "Query parameters for the bounded Memory staging list.", + "properties": { + "limit": { + "format": "uint32", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "MemoryStagingReadOperation": { + "properties": { + "candidate_id": { + "type": "string" + } + }, + "required": [ + "candidate_id" + ], + "type": "object" + }, + "MemoryStagingRecord": { + "additionalProperties": false, + "description": "Public projection of one valid Memory staging record.", + "properties": { + "claim": { + "type": "string" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/MemoryStagingEvidence" + }, + "type": "array" + }, + "extract_run_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/MemoryCandidateKind" + }, + "schema_version": { + "format": "uint32", + "minimum": 0, + "type": "integer" + }, + "source": { + "$ref": "#/components/schemas/MemorySourceRef" + }, + "source_refs": { + "items": { + "$ref": "#/components/schemas/MemorySourceEvidenceRef" + }, + "type": "array" + }, + "staleness": { + "type": [ + "string", + "null" + ] + }, + "why_useful": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "extract_run_id", + "source", + "kind", + "claim", + "why_useful", + "evidence", + "source_refs" + ], + "type": "object" + }, + "MemoryStagingWriteOutput": { + "properties": { + "staging_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "staging_ids": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "staging_count", + "staging_ids" + ], + "type": "object" + }, + "MemoryToolOutput": { + "properties": { + "content": { + "type": [ + "string", + "null" + ] + }, + "summary": { + "type": "string" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + "ModelAudit": { + "properties": { + "model_id": { + "type": [ + "string", + "null" + ] + }, + "ref_": { + "type": [ + "string", + "null" + ] + }, + "scheme": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "OperationCounts": { + "properties": { + "delete": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "drop": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "edit": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "merge": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "trim": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "write": { + "default": 0, + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "type": "object" + }, "PasskeyLoginOptionsRequest": { "additionalProperties": false, "properties": { @@ -457,6 +2345,263 @@ ], "type": "object" }, + "ProfileSettingsResponse": { + "additionalProperties": false, + "description": "Read-only Profile catalog projected from one active Workspace config revision.", + "properties": { + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "default_profile": { + "type": [ + "string", + "null" + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "profiles": { + "items": { + "$ref": "#/components/schemas/WorkspaceProfileSummary" + }, + "type": "array" + }, + "projection_digest": { + "type": [ + "string", + "null" + ] + }, + "registry_revision": { + "type": "string" + }, + "sources": { + "items": { + "$ref": "#/components/schemas/WorkspaceProfileSourceSummary" + }, + "type": "array" + }, + "tree_digest": { + "type": [ + "string", + "null" + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "registry_revision", + "profiles", + "sources", + "diagnostics" + ], + "type": "object" + }, + "PutFlowRequest": { + "additionalProperties": false, + "properties": { + "content": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path", + "content" + ], + "type": "object" + }, + "PutRepositorySshHostTrustRequest": { + "additionalProperties": false, + "properties": { + "expected_revision": { + "default": null, + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "host_key": { + "type": "string" + }, + "host_trust_id": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "operation_id": { + "type": "string" + }, + "port": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "operation_id", + "host_trust_id", + "hostname", + "port", + "host_key" + ], + "type": "object" + }, + "RecordOperationAudit": { + "properties": { + "after_hash": { + "type": [ + "string", + "null" + ] + }, + "before_hash": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string" + }, + "op": { + "type": "string" + }, + "path": { + "type": "string" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AuditStatus" + } + }, + "required": [ + "op", + "status", + "kind", + "slug", + "path" + ], + "type": "object" + }, + "RecordUsageAudit": { + "properties": { + "kind": { + "type": "string" + }, + "op": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "result_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "slug": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/AuditStatus" + } + }, + "required": [ + "op", + "status", + "kind" + ], + "type": "object" + }, + "RepositoryAccessMode": { + "enum": [ + "read_only", + "read_write" + ], + "type": "string" + }, + "RepositoryAccessProjection": { + "additionalProperties": false, + "description": "Secret-free active Repository access projection consumed by later Runtime work.", + "properties": { + "bindings": { + "items": { + "$ref": "#/components/schemas/RepositorySshAccessBinding" + }, + "type": "array" + }, + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "projection_digest": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "config_revision", + "projection_digest", + "bindings" + ], + "type": "object" + }, "RepositoryApiError": { "additionalProperties": false, "description": "Error body shared by the existing repository routes and their generated adapters.", @@ -595,6 +2740,242 @@ } ] }, + "RepositorySshAccessBinding": { + "additionalProperties": false, + "properties": { + "access": { + "$ref": "#/components/schemas/RepositoryAccessMode" + }, + "credential_id": { + "type": "string" + }, + "host_trust_id": { + "type": "string" + }, + "repository_key": { + "type": "string" + } + }, + "required": [ + "repository_key", + "credential_id", + "host_trust_id", + "access" + ], + "type": "object" + }, + "RepositorySshCredential": { + "additionalProperties": false, + "description": "Public metadata for one Workspace-scoped Repository SSH credential.\n\nSecret references and secret material are deliberately not part of this DTO.", + "properties": { + "created_at": { + "type": "string" + }, + "credential_id": { + "type": "string" + }, + "current_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "public_key_algorithm": { + "type": "string" + }, + "public_key_fingerprint": { + "type": "string" + }, + "referenced_repositories": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "rotated_at": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "credential_id", + "workspace_id", + "name", + "public_key_algorithm", + "public_key_fingerprint", + "current_revision", + "status", + "created_at" + ], + "type": "object" + }, + "RepositorySshHostTrust": { + "additionalProperties": false, + "description": "Public metadata for an explicitly pinned SSH host key.", + "properties": { + "created_at": { + "type": "string" + }, + "current_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "host_key": { + "type": "string" + }, + "host_trust_id": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "key_algorithm": { + "type": "string" + }, + "port": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "referenced_repositories": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "host_trust_id", + "workspace_id", + "hostname", + "port", + "key_algorithm", + "host_key", + "fingerprint", + "current_revision", + "created_at", + "updated_at" + ], + "type": "object" + }, + "RepositorySshHostTrustMutationResponse": { + "additionalProperties": false, + "description": "Public metadata for an explicitly pinned SSH host key.", + "properties": { + "created_at": { + "type": "string" + }, + "current_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "host_key": { + "type": "string" + }, + "host_trust_id": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "key_algorithm": { + "type": "string" + }, + "port": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "referenced_repositories": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "host_trust_id", + "workspace_id", + "hostname", + "port", + "key_algorithm", + "host_key", + "fingerprint", + "current_revision", + "created_at", + "updated_at" + ], + "type": "object" + }, + "RepositorySshPublicKey": { + "additionalProperties": false, + "properties": { + "credential_id": { + "type": "string" + }, + "current_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "public_key": { + "type": "string" + }, + "public_key_algorithm": { + "type": "string" + }, + "public_key_fingerprint": { + "type": "string" + } + }, + "required": [ + "credential_id", + "current_revision", + "public_key_algorithm", + "public_key_fingerprint", + "public_key" + ], + "type": "object" + }, "RepositorySummary": { "additionalProperties": false, "properties": { @@ -698,6 +3079,669 @@ ], "type": "object" }, + "ResolvedFlowSource": { + "additionalProperties": false, + "properties": { + "content_digest": { + "type": "string" + }, + "definition": { + "$ref": "#/components/schemas/CompiledFlowDefinition" + }, + "flow_id": { + "type": "string" + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "selector": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "selector", + "workspace_id", + "flow_id", + "revision", + "content_digest", + "definition" + ], + "type": "object" + }, + "RotateRepositorySshCredentialRequest": { + "additionalProperties": false, + "properties": { + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "operation_id": { + "type": "string" + }, + "passphrase": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "private_key": { + "type": "string" + } + }, + "required": [ + "operation_id", + "expected_revision", + "private_key" + ], + "type": "object" + }, + "ServerJsonSafeU64": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "SkillActivationResponse": { + "additionalProperties": false, + "properties": { + "body": { + "type": "string" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/SkillDiagnostic" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "provenance": { + "$ref": "#/components/schemas/SkillProvenance" + } + }, + "required": [ + "name", + "provenance", + "body" + ], + "type": "object" + }, + "SkillActivationStatus": { + "enum": [ + "active", + "inactive" + ], + "type": "string" + }, + "SkillCatalogEntry": { + "additionalProperties": false, + "properties": { + "activation_status": { + "$ref": "#/components/schemas/SkillActivationStatus" + }, + "description": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/SkillDiagnostic" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "overrides": { + "items": { + "$ref": "#/components/schemas/SkillProvenance" + }, + "type": "array" + }, + "projection_status": { + "$ref": "#/components/schemas/SkillProjectionStatus" + }, + "provenance": { + "$ref": "#/components/schemas/SkillProvenance" + } + }, + "required": [ + "name", + "description", + "activation_status", + "projection_status", + "provenance", + "overrides", + "diagnostics" + ], + "type": "object" + }, + "SkillCatalogResponse": { + "additionalProperties": false, + "properties": { + "authority": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/SkillDiagnostic" + }, + "type": "array" + }, + "entries": { + "items": { + "$ref": "#/components/schemas/SkillCatalogEntry" + }, + "type": "array" + }, + "projection": { + "$ref": "#/components/schemas/SkillProjectionIdentity" + } + }, + "required": [ + "authority", + "projection", + "entries", + "diagnostics" + ], + "type": "object" + }, + "SkillDetailResponse": { + "additionalProperties": false, + "properties": { + "activation_status": { + "$ref": "#/components/schemas/SkillActivationStatus" + }, + "allowed_tools": { + "items": { + "type": "string" + }, + "type": "array" + }, + "allowed_tools_status": { + "type": "string" + }, + "authority": { + "type": "string" + }, + "body": { + "type": "string" + }, + "description": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/SkillDiagnostic" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "overrides": { + "items": { + "$ref": "#/components/schemas/SkillProvenance" + }, + "type": "array" + }, + "projection": { + "$ref": "#/components/schemas/SkillProjectionIdentity" + }, + "projection_status": { + "$ref": "#/components/schemas/SkillProjectionStatus" + }, + "provenance": { + "$ref": "#/components/schemas/SkillProvenance" + }, + "resources": { + "items": { + "$ref": "#/components/schemas/SkillResourceRef" + }, + "type": "array" + } + }, + "required": [ + "authority", + "projection", + "name", + "description", + "provenance", + "overrides", + "diagnostics", + "activation_status", + "projection_status", + "body", + "allowed_tools", + "allowed_tools_status", + "resources" + ], + "type": "object" + }, + "SkillDiagnostic": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "severity": { + "$ref": "#/components/schemas/SkillDiagnosticSeverity" + }, + "source": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "severity", + "code", + "message" + ], + "type": "object" + }, + "SkillDiagnosticSeverity": { + "enum": [ + "error", + "warning" + ], + "type": "string" + }, + "SkillProjectionIdentity": { + "additionalProperties": false, + "properties": { + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "tree_digest": { + "type": "string" + } + }, + "required": [ + "config_revision", + "tree_digest" + ], + "type": "object" + }, + "SkillProjectionStatus": { + "enum": [ + "valid", + "invalid" + ], + "type": "string" + }, + "SkillProvenance": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/SkillSourceKind" + }, + "revision": { + "format": "uint64", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "source_digest": { + "type": [ + "string", + "null" + ] + }, + "tree_digest": { + "type": [ + "string", + "null" + ] + }, + "virtual_path": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "id" + ], + "type": "object" + }, + "SkillResourceRef": { + "additionalProperties": false, + "properties": { + "diagnostic": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "supported": { + "type": "boolean" + } + }, + "required": [ + "kind", + "name", + "supported" + ], + "type": "object" + }, + "SkillSourceKind": { + "enum": [ + "builtin", + "workspace" + ], + "type": "string" + }, + "SourceEvidenceRef": { + "additionalProperties": false, + "description": "Host-resolved source/evidence metadata for an individual staging claim.\n\nThis deliberately stores only bounded anchor metadata: stable ids, entry\nranges, and short labels/summaries. It must not carry raw message bodies or\nfull tool result content.", + "properties": { + "entry_range": { + "description": "`[start_entry, end_entry]` inclusive range of session-store entry indices.", + "items": { + "$ref": "#/components/schemas/MemoryJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "evidence_id": { + "description": "Host-assigned evidence id within the referenced evidence set.", + "type": [ + "string", + "null" + ] + }, + "evidence_kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/EvidenceKind" + }, + { + "type": "null" + } + ], + "description": "Extensible evidence kind tag." + }, + "label": { + "description": "Short host-provided display label, not raw evidence content.", + "type": [ + "string", + "null" + ] + }, + "origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/EvidenceOrigin" + }, + { + "type": "null" + } + ], + "description": "Trusted typed origin snapshot for this logical evidence entry." + }, + "segment_id": { + "description": "Session segment id containing the anchored log entries.", + "type": [ + "string", + "null" + ] + }, + "session_id": { + "description": "Stable session id when the anchor crosses or disambiguates segments.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Short host-provided summary, not raw evidence content.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SourceRef": { + "description": "Reference to a session-store entry range. Stored in `sources` /\n`last_sources` arrays for traceability back to raw session logs.", + "properties": { + "range": { + "description": "`[start_entry, end_entry]` inclusive range of session-store entry indices.", + "items": { + "$ref": "#/components/schemas/MemoryJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "segment_id": { + "type": "string" + } + }, + "required": [ + "segment_id", + "range" + ], + "type": "object" + }, + "StagingEvidence": { + "additionalProperties": false, + "description": "Bounded evidence snippet copied into a flat staging record.", + "properties": { + "entry_range": { + "items": { + "$ref": "#/components/schemas/MemoryJsonSafeU64" + }, + "maxItems": 2, + "minItems": 2, + "type": [ + "array", + "null" + ] + }, + "excerpt": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/EvidenceKind" + }, + "origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/EvidenceOrigin" + }, + { + "type": "null" + } + ] + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, + "ToolchainContract": { + "additionalProperties": false, + "properties": { + "contract_version": { + "format": "uint32", + "minimum": 0, + "type": "integer" + }, + "decodal_version": { + "type": "string" + }, + "entrypoints": { + "items": { + "type": "string" + }, + "type": "array" + }, + "fingerprint": { + "type": "string" + }, + "import_policy_version": { + "format": "uint32", + "minimum": 0, + "type": "integer" + }, + "schema_bundle": { + "$ref": "#/components/schemas/WorkspaceConfigSchemaBundle" + }, + "schema_version": { + "format": "uint32", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "contract_version", + "decodal_version", + "schema_version", + "entrypoints", + "import_policy_version", + "schema_bundle", + "fingerprint" + ], + "type": "object" + }, + "UpdateWorkspaceMemorySettingsRequest": { + "additionalProperties": false, + "description": "Compare-and-swap update for Workspace-owned Memory settings.", + "properties": { + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "language": { + "type": "string" + } + }, + "required": [ + "expected_revision", + "language" + ], + "type": "object" + }, + "UpdateWorkspaceMetadataRequest": { + "additionalProperties": false, + "description": "Compare-and-swap update for Workspace identity display metadata.", + "properties": { + "display_name": { + "type": "string" + }, + "revision": { + "type": "string" + } + }, + "required": [ + "display_name", + "revision" + ], + "type": "object" + }, + "UsageAudit": { + "properties": { + "cache_creation_input_tokens": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "cache_read_input_tokens": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "input_tokens": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "output_tokens": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "total_tokens": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, "WhoamiResponse": { "additionalProperties": false, "properties": { @@ -714,6 +3758,130 @@ }, "type": "object" }, + "WorkerLifecycleAudit": { + "properties": { + "consolidation": { + "anyOf": [ + { + "$ref": "#/components/schemas/ConsolidationAudit" + }, + { + "type": "null" + } + ] + }, + "extract": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExtractAudit" + }, + { + "type": "null" + } + ] + }, + "memory_settings": { + "anyOf": [ + { + "$ref": "#/components/schemas/MemorySettingsAudit" + }, + { + "type": "null" + } + ] + }, + "model": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelAudit" + }, + { + "type": "null" + } + ] + }, + "reason": { + "type": "string" + }, + "run_id": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/WorkerLifecycleStatus" + }, + "trigger": { + "$ref": "#/components/schemas/AuditTrigger" + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/UsageAudit" + }, + { + "type": "null" + } + ] + }, + "worker": { + "$ref": "#/components/schemas/AuditWorker" + } + }, + "required": [ + "run_id", + "worker", + "status", + "trigger", + "reason" + ], + "type": "object" + }, + "WorkerLifecycleStatus": { + "enum": [ + "started", + "completed", + "skipped", + "failed", + "cancelled" + ], + "type": "string" + }, + "WorkspaceAuthConfig": { + "description": "Browser authentication configuration exposed by the scoped Workspace summary.", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "Passkey": { + "properties": { + "cookie_name": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "public_base_url": { + "type": "string" + }, + "rp_id": { + "type": "string" + } + }, + "required": [ + "rp_id", + "origin", + "public_base_url", + "cookie_name" + ], + "type": "object" + } + }, + "required": [ + "Passkey" + ], + "type": "object" + } + ] + }, "WorkspaceCatalogListResponse": { "description": "Public response returned by `GET /api/workspaces`.\n\nThe transparent newtype keeps the established top-level JSON array while making the\ncomplete list response a named cross-crate and generated-TypeScript authority.", "items": { @@ -721,6 +3889,50 @@ }, "type": "array" }, + "WorkspaceConfigSchemaBundle": { + "additionalProperties": false, + "properties": { + "contributions": { + "items": { + "$ref": "#/components/schemas/ConfigSchemaContribution" + }, + "type": "array" + }, + "fingerprint": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "contributions", + "source", + "fingerprint" + ], + "type": "object" + }, + "WorkspaceConfigTreeResponse": { + "additionalProperties": false, + "description": "Workspace configuration tree plus the exact evaluation contract that produced it.", + "properties": { + "contract": { + "$ref": "#/components/schemas/ToolchainContract" + }, + "projection_digest": { + "type": "string" + }, + "snapshot": { + "$ref": "#/components/schemas/ConfigTreeSnapshot" + } + }, + "required": [ + "snapshot", + "contract", + "projection_digest" + ], + "type": "object" + }, "WorkspaceCreateRequest": { "additionalProperties": false, "description": "Request for atomically creating a Workspace and its initial Repository.", @@ -999,6 +4211,53 @@ ], "type": "string" }, + "WorkspaceExtensionPointState": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status", + "note", + "diagnostics" + ], + "type": "object" + }, + "WorkspaceExtensionPoints": { + "additionalProperties": false, + "properties": { + "companion_console": { + "$ref": "#/components/schemas/WorkspaceExtensionPointState" + }, + "event_stream": { + "$ref": "#/components/schemas/WorkspaceExtensionPointState" + }, + "host_worker_bridge": { + "$ref": "#/components/schemas/WorkspaceExtensionPointState" + }, + "store": { + "type": "string" + } + }, + "required": [ + "store", + "event_stream", + "host_worker_bridge", + "companion_console" + ], + "type": "object" + }, "WorkspaceListQuery": { "additionalProperties": false, "properties": { @@ -1013,6 +4272,301 @@ }, "type": "object" }, + "WorkspaceMemorySettings": { + "additionalProperties": false, + "description": "Workspace-owned Memory settings returned by the shared Server API.", + "properties": { + "language": { + "type": "string" + }, + "settings_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "settings_revision", + "language" + ], + "type": "object" + }, + "WorkspaceMetadataMutationResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "workspace": { + "$ref": "#/components/schemas/WorkspaceMetadataSettingsResponse" + } + }, + "required": [ + "workspace", + "diagnostics" + ], + "type": "object" + }, + "WorkspaceMetadataSettingsResponse": { + "additionalProperties": false, + "description": "Workspace display metadata exposed from the Server DB settings authority.", + "properties": { + "created_at": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "display_name": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "display_name", + "created_at", + "revision", + "source", + "diagnostics" + ], + "type": "object" + }, + "WorkspacePermissionSummary": { + "additionalProperties": false, + "description": "Backend-authoritative permissions for the current Workspace actor.", + "properties": { + "delete_workspace": { + "type": "boolean" + }, + "manage_repositories": { + "type": "boolean" + }, + "manage_runtimes": { + "type": "boolean" + }, + "manage_secrets": { + "type": "boolean" + } + }, + "required": [ + "manage_repositories", + "manage_secrets", + "manage_runtimes", + "delete_workspace" + ], + "type": "object" + }, + "WorkspaceProfileSourceProvenance": { + "enum": [ + "project_profile_source_tree" + ], + "type": "string" + }, + "WorkspaceProfileSourceSummary": { + "additionalProperties": false, + "properties": { + "content_digest": { + "type": "string" + }, + "content_type": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "display_path": { + "type": "string" + }, + "editable": { + "type": "boolean" + }, + "kind": { + "type": "string" + }, + "profile_source_id": { + "type": "string" + }, + "provenance": { + "$ref": "#/components/schemas/WorkspaceProfileSourceProvenance" + }, + "revision": { + "type": "string" + }, + "size_bytes": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "profile_source_id", + "display_path", + "kind", + "content_type", + "content_digest", + "provenance", + "editable", + "revision", + "size_bytes", + "diagnostics" + ], + "type": "object" + }, + "WorkspaceProfileSummary": { + "additionalProperties": false, + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "editable": { + "type": "boolean" + }, + "is_default": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "profile_id": { + "type": "string" + }, + "profile_source_id": { + "type": [ + "string", + "null" + ] + }, + "selector": { + "type": "string" + }, + "source_kind": { + "type": "string" + } + }, + "required": [ + "profile_id", + "selector", + "label", + "source_kind", + "editable", + "is_default", + "diagnostics" + ], + "type": "object" + }, + "WorkspacePromptProjection": { + "additionalProperties": false, + "description": "Effective Prompt catalog projected from one immutable Workspace config revision.", + "properties": { + "catalog": { + "$ref": "#/components/schemas/EffectivePromptCatalog" + }, + "config_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "projection_digest": { + "type": "string" + }, + "schema_fingerprint": { + "type": "string" + }, + "source_digest": { + "type": "string" + }, + "toolchain_fingerprint": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "config_revision", + "source_digest", + "projection_digest", + "schema_fingerprint", + "toolchain_fingerprint", + "catalog" + ], + "type": "object" + }, + "WorkspacePublicIdentityBundle": { + "additionalProperties": false, + "description": "Copyable public trust bundle consumed by future Runtime enrollment work.", + "properties": { + "algorithm": { + "type": "string" + }, + "backend_url": { + "type": "string" + }, + "key_id": { + "type": "string" + }, + "public_key": { + "type": "string" + }, + "public_key_fingerprint": { + "type": "string" + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "backend_url", + "key_id", + "algorithm", + "public_key", + "public_key_fingerprint", + "revision" + ], + "type": "object" + }, "WorkspaceRepositoryRecord": { "additionalProperties": false, "description": "Public Repository record embedded in Workspace creation responses.", @@ -1079,6 +4633,130 @@ ], "type": "object" }, + "WorkspaceResponse": { + "additionalProperties": false, + "description": "Scoped Workspace metadata and current-actor permission projection.", + "properties": { + "auth": { + "$ref": "#/components/schemas/WorkspaceAuthConfig" + }, + "display_name": { + "type": "string" + }, + "extension_points": { + "$ref": "#/components/schemas/WorkspaceExtensionPoints" + }, + "permissions": { + "$ref": "#/components/schemas/WorkspacePermissionSummary" + }, + "record_authority": { + "type": "string" + }, + "schema_version": { + "format": "int64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "display_name", + "record_authority", + "schema_version", + "auth", + "permissions", + "extension_points" + ], + "type": "object" + }, + "WorkspaceSigningIdentityPublic": { + "additionalProperties": false, + "description": "Public metadata for a Workspace signing identity. Private material and its\nstorage reference are deliberately not part of this wire authority.", + "properties": { + "algorithm": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "key_id": { + "type": "string" + }, + "provisioned_at": { + "type": [ + "string", + "null" + ] + }, + "public_key": { + "type": [ + "string", + "null" + ] + }, + "public_key_fingerprint": { + "type": [ + "string", + "null" + ] + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "state": { + "$ref": "#/components/schemas/WorkspaceSigningIdentityState" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "key_id", + "algorithm", + "revision", + "state", + "created_at" + ], + "type": "object" + }, + "WorkspaceSigningIdentityResponse": { + "additionalProperties": false, + "properties": { + "identity": { + "$ref": "#/components/schemas/WorkspaceSigningIdentityPublic" + }, + "public_bundle": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkspacePublicIdentityBundle" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "identity" + ], + "type": "object" + }, + "WorkspaceSigningIdentityState": { + "description": "Lifecycle state for a Workspace-scoped Ed25519 signing identity.", + "enum": [ + "pending_provisioning", + "active" + ], + "type": "string" + }, "WorkspaceSummary": { "additionalProperties": false, "description": "Public Workspace catalog item returned by `GET /api/workspaces`.", @@ -1131,7 +4809,7 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:b478a5e12b43d9ac1261ba31358a5b80bb31e398bd57eccbd797f5e92c6f48b7" + "x-yoi-source-digest": "sha256:386c56c830b49766e9f523f08c5bd4e24de490ba18c99c3d04063021d6b33352" }, "openapi": "3.1.0", "paths": { @@ -1711,6 +5389,1219 @@ ] } }, + "/api/w/{workspace_id}/config/projections/prompts": { + "get": { + "operationId": "workspace_prompt_projection", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspacePromptProjection" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/config/source-tree": { + "get": { + "operationId": "workspace_config_tree", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceConfigTreeResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/config/source-tree/commit": { + "post": { + "operationId": "workspace_config_tree_commit", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigCommitRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceConfigTreeResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/config/source-tree/entries/{path}": { + "get": { + "operationId": "workspace_config_entry", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigEntry" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/config/source-tree/revisions/{revision}": { + "get": { + "operationId": "workspace_config_revision", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "revision", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigTreeSnapshot" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/flows": { + "get": { + "operationId": "flow_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_FlowSourceRecord" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + }, + "put": { + "operationId": "flow_put", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutFlowRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FlowSourceRecord" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/flows/resolve": { + "post": { + "operationId": "flow_resolve", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FlowSourceResolveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolvedFlowSource" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/flows/{flow_id}": { + "get": { + "operationId": "flow_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "flow_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FlowSourceRecord" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/memory": { + "get": { + "operationId": "memory_document", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryDocumentResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/memory/backend": { + "post": { + "operationId": "memory_backend", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryBackendRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryBackendResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/memory/consolidation": { + "post": { + "operationId": "memory_consolidation", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryConsolidateStagingRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryConsolidationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/memory/staging": { + "get": { + "operationId": "memory_staging_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/MemoryStagingQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryStagingListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/repositories": { "get": { "operationId": "repository_list", @@ -2040,6 +6931,2357 @@ ] } }, + "/api/w/{workspace_id}/settings": { + "get": { + "operationId": "workspace_metadata_settings", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceMetadataSettingsResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "put": { + "operationId": "workspace_metadata_settings_update", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWorkspaceMetadataRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceMetadataMutationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/memory": { + "get": { + "operationId": "workspace_memory_settings", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceMemorySettings" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "put": { + "operationId": "workspace_memory_settings_update", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWorkspaceMemorySettingsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceMemorySettings" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/profiles": { + "get": { + "operationId": "profile_settings", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileSettingsResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access": { + "get": { + "operationId": "repository_access_projection", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryAccessProjection" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/credentials": { + "get": { + "operationId": "repository_ssh_credential_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_RepositorySshCredential" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "repository_ssh_credential_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRepositorySshCredentialRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshCredential" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/credentials/generate": { + "post": { + "operationId": "repository_ssh_credential_generate", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateRepositorySshCredentialRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshCredential" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}": { + "delete": { + "operationId": "repository_ssh_credential_delete", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "credential_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteRepositorySshCredentialRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "repository_ssh_credential_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "credential_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshCredential" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/public-key": { + "get": { + "operationId": "repository_ssh_credential_public_key", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "credential_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshPublicKey" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/credentials/{credential_id}/rotate": { + "post": { + "operationId": "repository_ssh_credential_rotate", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "credential_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateRepositorySshCredentialRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshCredential" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/host-trusts": { + "get": { + "operationId": "repository_ssh_host_trust_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_RepositorySshHostTrust" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "repository_ssh_host_trust_put", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutRepositorySshHostTrustRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshHostTrustMutationResponse" + } + } + }, + "description": "Alternate successful response" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshHostTrustMutationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/repository-access/host-trusts/{host_trust_id}": { + "delete": { + "operationId": "repository_ssh_host_trust_delete", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "host_trust_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteRepositorySshHostTrustRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "repository_ssh_host_trust_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "host_trust_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshHostTrust" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/signing-identity": { + "get": { + "operationId": "workspace_signing_identity", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceSigningIdentityResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/settings/signing-identity/provision": { + "post": { + "operationId": "workspace_signing_identity_provision", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceSigningIdentityResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/skills": { + "get": { + "operationId": "skill_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillCatalogResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/skills/lint": { + "get": { + "operationId": "skill_lint", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillCatalogResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/skills/{name}": { + "get": { + "operationId": "skill_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "name", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillDetailResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/skills/{name}/activate": { + "get": { + "operationId": "skill_activate", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "name", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillActivationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/workspace": { + "get": { + "operationId": "workspace_scoped", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/workspace": { + "get": { + "operationId": "workspace_current", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/workspace-deletions/{operation_id}": { "get": { "operationId": "workspace_deletion_get", diff --git a/web/workspace/deno.json b/web/workspace/deno.json index 3b9e7853b..7eb9b9038 100644 --- a/web/workspace/deno.json +++ b/web/workspace/deno.json @@ -6,7 +6,7 @@ "dev": "deno run -A npm:vite@7.2.7 dev", "dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787", "check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json", - "test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/repository-api.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/console/run-status.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-workdir-meta.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts", + "test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/repository-api.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/console/run-status.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-workdir-meta.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/config-source/api.test.ts tests/memory-api.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts", "test:component": "deno run -A npm:vitest@3.2.4 run --config vitest.config.ts", "build": "deno run -A npm:vite@7.2.7 build", "preview": "deno run -A npm:vite@7.2.7 preview", diff --git a/web/workspace/src/lib/generated/legacy-server-api.ts b/web/workspace/src/lib/generated/legacy-server-api.ts index 7c4b0b9df..dd34705e3 100644 --- a/web/workspace/src/lib/generated/legacy-server-api.ts +++ b/web/workspace/src/lib/generated/legacy-server-api.ts @@ -1,140 +1,565 @@ // This file is generated by `cargo run -p server-api --features typescript --example generate_legacy_typescript | deno fmt -`. // Do not edit this file directly. -import type { RepositoryObservedStatus, RepositorySource } from "./repository-api"; +import type { + RepositoryObservedStatus, + RepositorySource, +} from "./repository-api"; -export type WorkspaceSummary = { workspace_id: string, owner_account_id: string, display_name: string, state: string, created_at: string, updated_at: string, }; +export type WorkspaceSummary = { + workspace_id: string; + owner_account_id: string; + display_name: string; + state: string; + created_at: string; + updated_at: string; +}; export type WorkspaceCatalogListResponse = Array; -export type WorkspaceRepositoryRecord = { workspace_id: string, repository_key: string, kind: string, provider: string | null, source: RepositorySource, default_ref: string | null, source_revision: number, source_fingerprint: string, observed_status: RepositoryObservedStatus, observed_at: string | null, created_at: string, updated_at: string, }; +export type WorkspaceRepositoryRecord = { + workspace_id: string; + repository_key: string; + kind: string; + provider: string | null; + source: RepositorySource; + default_ref: string | null; + source_revision: number; + source_fingerprint: string; + observed_status: RepositoryObservedStatus; + observed_at: string | null; + created_at: string; + updated_at: string; +}; -export type WorkspaceCreateResponse = { workspace: WorkspaceSummary, repository: WorkspaceRepositoryRecord, config_revision: number, request_fingerprint: string, replayed: boolean, }; +export type WorkspaceCreateResponse = { + workspace: WorkspaceSummary; + repository: WorkspaceRepositoryRecord; + config_revision: number; + request_fingerprint: string; + replayed: boolean; +}; -export type WorkspaceAuthConfig = { "Passkey": { rp_id: string, origin: string, public_base_url: string, cookie_name: string, } }; +export type WorkspaceAuthConfig = { + "Passkey": { + rp_id: string; + origin: string; + public_base_url: string; + cookie_name: string; + }; +}; -export type WorkspacePermissionSummary = { manage_repositories: boolean, manage_secrets: boolean, manage_runtimes: boolean, delete_workspace: boolean, }; +export type WorkspacePermissionSummary = { + manage_repositories: boolean; + manage_secrets: boolean; + manage_runtimes: boolean; + delete_workspace: boolean; +}; -export type WorkspaceDeletionState = "queued" | "running" | "blocked" | "failed" | "succeeded"; +export type WorkspaceDeletionState = + | "queued" + | "running" + | "blocked" + | "failed" + | "succeeded"; -export type WorkspaceDeletionBlockerKind = "last_accessible_workspace" | "revision_conflict" | "dirty_workdir" | "worker_removal_blocked" | "workdir_removal_blocked" | "retention_hold" | "cleanup_unavailable"; +export type WorkspaceDeletionBlockerKind = + | "last_accessible_workspace" + | "revision_conflict" + | "dirty_workdir" + | "worker_removal_blocked" + | "workdir_removal_blocked" + | "retention_hold" + | "cleanup_unavailable"; -export type WorkspaceDeletionBlocker = { kind: WorkspaceDeletionBlockerKind, resource_kind: string | null, resource_key: string | null, message: string, }; +export type WorkspaceDeletionBlocker = { + kind: WorkspaceDeletionBlockerKind; + resource_kind: string | null; + resource_key: string | null; + message: string; +}; -export type WorkspaceDeletionResourceCounts = { workers: number, workdirs: number, repositories: number, runtime_bindings: number, secrets: number, artifacts: number, }; +export type WorkspaceDeletionResourceCounts = { + workers: number; + workdirs: number; + repositories: number; + runtime_bindings: number; + secrets: number; + artifacts: number; +}; -export type WorkspaceDeletionPreflightResponse = { workspace_id: string, display_name: string, -/** - * Opaque persisted Workspace metadata revision used as a CAS fence. - */ -expected_revision: string, can_delete: boolean, resources: WorkspaceDeletionResourceCounts, blockers: Array, }; +export type WorkspaceDeletionPreflightResponse = { + workspace_id: string; + display_name: string; + /** + * Opaque persisted Workspace metadata revision used as a CAS fence. + */ + expected_revision: string; + can_delete: boolean; + resources: WorkspaceDeletionResourceCounts; + blockers: Array; +}; -export type WorkspaceDeletionRequest = { operation_id: string, expected_revision: string, confirmation: string, }; +export type WorkspaceDeletionRequest = { + operation_id: string; + expected_revision: string; + confirmation: string; +}; -export type WorkspaceDeletionOperationResponse = { operation_id: string, workspace_id: string, display_name: string, state: WorkspaceDeletionState, resources: WorkspaceDeletionResourceCounts, child_operation_ids: Array, blockers: Array, failure_category: string | null, created_at: string, updated_at: string, completed_at: string | null, }; +export type WorkspaceDeletionOperationResponse = { + operation_id: string; + workspace_id: string; + display_name: string; + state: WorkspaceDeletionState; + resources: WorkspaceDeletionResourceCounts; + child_operation_ids: Array; + blockers: Array; + failure_category: string | null; + created_at: string; + updated_at: string; + completed_at: string | null; +}; export type DiagnosticSeverity = "info" | "warning" | "error"; -export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, }; +export type Diagnostic = { + code: string; + severity: DiagnosticSeverity; + message: string; +}; -export type WorkspaceExtensionPointState = { status: string, note: string, diagnostics: Array, }; +export type WorkspaceExtensionPointState = { + status: string; + note: string; + diagnostics: Array; +}; -export type WorkspaceExtensionPoints = { store: string, event_stream: WorkspaceExtensionPointState, host_worker_bridge: WorkspaceExtensionPointState, companion_console: WorkspaceExtensionPointState, }; +export type WorkspaceExtensionPoints = { + store: string; + event_stream: WorkspaceExtensionPointState; + host_worker_bridge: WorkspaceExtensionPointState; + companion_console: WorkspaceExtensionPointState; +}; -export type WorkspaceResponse = { workspace_id: string, display_name: string, record_authority: string, schema_version: number, auth: WorkspaceAuthConfig, permissions: WorkspacePermissionSummary, extension_points: WorkspaceExtensionPoints, }; +export type WorkspaceResponse = { + workspace_id: string; + display_name: string; + record_authority: string; + schema_version: number; + auth: WorkspaceAuthConfig; + permissions: WorkspacePermissionSummary; + extension_points: WorkspaceExtensionPoints; +}; -export type WorkspaceMetadataSettingsResponse = { workspace_id: string, display_name: string, created_at: string, revision: string, source: string, diagnostics: Array, }; +export type WorkspaceMetadataSettingsResponse = { + workspace_id: string; + display_name: string; + created_at: string; + revision: string; + source: string; + diagnostics: Array; +}; -export type UpdateWorkspaceMetadataRequest = { display_name: string, revision: string, }; +export type UpdateWorkspaceMetadataRequest = { + display_name: string; + revision: string; +}; -export type WorkspaceMetadataMutationResponse = { workspace: WorkspaceMetadataSettingsResponse, diagnostics: Array, }; +export type WorkspaceMetadataMutationResponse = { + workspace: WorkspaceMetadataSettingsResponse; + diagnostics: Array; +}; export type WorkspaceSigningIdentityState = "pending_provisioning" | "active"; -export type WorkspaceSigningIdentityPublic = { workspace_id: string, key_id: string, algorithm: string, public_key?: string, public_key_fingerprint?: string, revision: number, state: WorkspaceSigningIdentityState, created_at: string, provisioned_at?: string, }; +export type WorkspaceSigningIdentityPublic = { + workspace_id: string; + key_id: string; + algorithm: string; + public_key?: string; + public_key_fingerprint?: string; + revision: number; + state: WorkspaceSigningIdentityState; + created_at: string; + provisioned_at?: string; +}; -export type WorkspacePublicIdentityBundle = { workspace_id: string, backend_url: string, key_id: string, algorithm: string, public_key: string, public_key_fingerprint: string, revision: number, }; +export type WorkspacePublicIdentityBundle = { + workspace_id: string; + backend_url: string; + key_id: string; + algorithm: string; + public_key: string; + public_key_fingerprint: string; + revision: number; +}; -export type WorkspaceSigningIdentityResponse = { identity: WorkspaceSigningIdentityPublic, public_bundle?: WorkspacePublicIdentityBundle, }; +export type WorkspaceSigningIdentityResponse = { + identity: WorkspaceSigningIdentityPublic; + public_bundle?: WorkspacePublicIdentityBundle; +}; -export type ProfileSettingsResponse = { workspace_id: string, registry_revision: string, config_revision?: number | null, tree_digest?: string | null, projection_digest?: string | null, default_profile?: string | null, profiles: Array, sources: Array, diagnostics: Array, }; +export type ConfigContentType = "decodal" | "text"; -export type WorkspaceProfileSummary = { profile_id: string, selector: string, label: string, source_kind: string, profile_source_id?: string | null, description?: string | null, editable: boolean, is_default: boolean, diagnostics: Array, }; +export type ConfigEntry = { + path: string; + content_type: ConfigContentType; + content: string; + content_digest: string; +}; -export type WorkspaceProfileSourceSummary = { profile_source_id: string, display_path: string, kind: string, content_type: string, content_digest: string, provenance: WorkspaceProfileSourceProvenance, editable: boolean, revision: string, size_bytes: number, diagnostics: Array, }; +export type ConfigTreeSnapshot = { + revision: number; + digest: string; + entries: { [key in string]: ConfigEntry }; +}; + +export type ConfigTreeChange = + | { + "kind": "create"; + path: string; + content_type: ConfigContentType; + content: string; + } + | { "kind": "update"; path: string; expected_digest: string; content: string } + | { "kind": "rename"; from: string; to: string; expected_digest: string } + | { "kind": "delete"; path: string; expected_digest: string }; + +export type ConfigProjectionValidator = { + "kind": "static_template_catalog"; + namespace: string; + key_aliases?: { [key in string]: string }; +}; + +export type ConfigSchemaContribution = { + provider_id: string; + namespace: string; + version: string; + source: string; + projection_validator?: ConfigProjectionValidator; + source_digest: string; +}; + +export type WorkspaceConfigSchemaBundle = { + contributions: Array; + source: string; + fingerprint: string; +}; + +export type ToolchainContract = { + contract_version: number; + decodal_version: string; + schema_version: number; + entrypoints: Array; + import_policy_version: number; + schema_bundle: WorkspaceConfigSchemaBundle; + fingerprint: string; +}; + +export type WorkspaceConfigTreeResponse = { + snapshot: ConfigTreeSnapshot; + contract: ToolchainContract; + projection_digest: string; +}; + +export type ConfigCommitRequest = { + base_revision: number; + base_digest: string; + changes: Array; + entrypoints: Array; +}; + +export type ProfileSettingsResponse = { + workspace_id: string; + registry_revision: string; + config_revision?: number | null; + tree_digest?: string | null; + projection_digest?: string | null; + default_profile?: string | null; + profiles: Array; + sources: Array; + diagnostics: Array; +}; + +export type WorkspaceProfileSummary = { + profile_id: string; + selector: string; + label: string; + source_kind: string; + profile_source_id?: string | null; + description?: string | null; + editable: boolean; + is_default: boolean; + diagnostics: Array; +}; + +export type WorkspaceProfileSourceSummary = { + profile_source_id: string; + display_path: string; + kind: string; + content_type: string; + content_digest: string; + provenance: WorkspaceProfileSourceProvenance; + editable: boolean; + revision: string; + size_bytes: number; + diagnostics: Array; +}; export type WorkspaceProfileSourceProvenance = "project_profile_source_tree"; -export type GitCommitSummary = { hash: string, short_hash: string, summary: string, author_name: string, author_email: string, author_date: string, parents: Array, refs: Array, }; +export type GitCommitSummary = { + hash: string; + short_hash: string; + summary: string; + author_name: string; + author_email: string; + author_date: string; + parents: Array; + refs: Array; +}; -export type RepositorySshConnectionProbeRequest = { runtime_id: string, }; +export type RepositorySshConnectionProbeRequest = { runtime_id: string }; -export type RepositorySshHostKeyCandidate = { algorithm: string, host_key: string, fingerprint: string, }; +export type RepositorySshHostKeyCandidate = { + algorithm: string; + host_key: string; + fingerprint: string; +}; -export type RepositorySshConnectionTrustState = "untrusted" | "verified" | "changed"; +export type RepositorySshConnectionTrustState = + | "untrusted" + | "verified" + | "changed"; -export type RepositorySshConnectionProbeResponse = { workspace_id: string, repository_key: string, runtime_id: string, hostname: string, port: number, trust_state: RepositorySshConnectionTrustState, host_trust_id: string, expected_host_trust_revision: number | null, candidates: Array, }; +export type RepositorySshConnectionProbeResponse = { + workspace_id: string; + repository_key: string; + runtime_id: string; + hostname: string; + port: number; + trust_state: RepositorySshConnectionTrustState; + host_trust_id: string; + expected_host_trust_revision: number | null; + candidates: Array; +}; -export type ConfirmRepositorySshHostTrustRequest = { operation_id: string, runtime_id: string, host_key: string, expected_host_trust_revision: number | null, }; +export type ConfirmRepositorySshHostTrustRequest = { + operation_id: string; + runtime_id: string; + host_key: string; + expected_host_trust_revision: number | null; +}; -export type RepositoryLogResponse = { workspace_id: string, repository_key: string, default_selector?: string | null, limit: number, items: Array, diagnostics: Array, }; +export type RepositoryLogResponse = { + workspace_id: string; + repository_key: string; + default_selector?: string | null; + limit: number; + items: Array; + diagnostics: Array; +}; export type RuntimeSourceKind = "embedded_worker_runtime" | "remote_http"; export type RuntimeSourceStatus = "active" | "reserved"; -export type RuntimeIdentityAuthority = "runtime_registry_projection" | "server_runtime_configuration"; +export type RuntimeIdentityAuthority = + | "runtime_registry_projection" + | "server_runtime_configuration"; -export type RuntimeSourceSummary = { kind: RuntimeSourceKind, status: RuntimeSourceStatus, identity_authority: RuntimeIdentityAuthority, note: string, }; +export type RuntimeSourceSummary = { + kind: RuntimeSourceKind; + status: RuntimeSourceStatus; + identity_authority: RuntimeIdentityAuthority; + note: string; +}; -export type RuntimeSummary = { runtime_id: string, label: string, kind: string, status: string, source: RuntimeSourceSummary, host_ids: Array, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array, }; +export type RuntimeSummary = { + runtime_id: string; + label: string; + kind: string; + status: string; + source: RuntimeSourceSummary; + host_ids: Array; + worker_creation_available: boolean; + os: string; + arch: string; + diagnostics: Array; +}; -export type WorkspaceRuntimeBindingState = "configured" | "verified" | "revoked"; +export type WorkspaceRuntimeBindingState = + | "configured" + | "verified" + | "revoked"; -export type RuntimeConnectionDisplayState = "configured" | "verified" | "unavailable" | "revoked"; +export type RuntimeConnectionDisplayState = + | "configured" + | "verified" + | "unavailable" + | "revoked"; -export type RuntimeVerificationOutcome = "verified" | "challenge_issued" | "verification_failed" | "connectivity_failed"; +export type RuntimeVerificationOutcome = + | "verified" + | "challenge_issued" + | "verification_failed" + | "connectivity_failed"; -export type RuntimeVerificationEvidenceSummary = { verified_at: string | null, last_checked_at: string, last_outcome: RuntimeVerificationOutcome, binding_revision: number, workspace_key_id: string, workspace_identity_revision: number, workspace_trust_generation: number, runtime_public_key_fingerprint: string, runtime_identity_revision: number, }; +export type RuntimeVerificationEvidenceSummary = { + verified_at: string | null; + last_checked_at: string; + last_outcome: RuntimeVerificationOutcome; + binding_revision: number; + workspace_key_id: string; + workspace_identity_revision: number; + workspace_trust_generation: number; + runtime_public_key_fingerprint: string; + runtime_identity_revision: number; +}; -export type WorkspaceRuntimeBindingSummary = { state: WorkspaceRuntimeBindingState, connection_state: RuntimeConnectionDisplayState, revision: number, workspace_key_id?: string | null, workspace_key_generation?: number | null, verification?: RuntimeVerificationEvidenceSummary | null, }; +export type WorkspaceRuntimeBindingSummary = { + state: WorkspaceRuntimeBindingState; + connection_state: RuntimeConnectionDisplayState; + revision: number; + workspace_key_id?: string | null; + workspace_key_generation?: number | null; + verification?: RuntimeVerificationEvidenceSummary | null; +}; -export type RuntimeManagementSummary = { built_in: boolean, config_managed: boolean, removable: boolean, endpoint_configured: boolean, token_ref_configured: boolean, binding?: WorkspaceRuntimeBindingSummary | null, }; +export type RuntimeManagementSummary = { + built_in: boolean; + config_managed: boolean; + removable: boolean; + endpoint_configured: boolean; + token_ref_configured: boolean; + binding?: WorkspaceRuntimeBindingSummary | null; +}; -export type WorkspaceRuntimeResource = { management: RuntimeManagementSummary, runtime_id: string, label: string, kind: string, status: string, source: RuntimeSourceSummary, host_ids: Array, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array, }; +export type WorkspaceRuntimeResource = { + management: RuntimeManagementSummary; + runtime_id: string; + label: string; + kind: string; + status: string; + source: RuntimeSourceSummary; + host_ids: Array; + worker_creation_available: boolean; + os: string; + arch: string; + diagnostics: Array; +}; export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked"; -export type RuntimeTrustKeyState = { status: RuntimeTrustKeyStatus, fingerprint?: string | null, revision?: number | null, created_at?: string | null, updated_at?: string | null, revoked_at?: string | null, }; +export type RuntimeTrustKeyState = { + status: RuntimeTrustKeyStatus; + fingerprint?: string | null; + revision?: number | null; + created_at?: string | null; + updated_at?: string | null; + revoked_at?: string | null; +}; -export type RuntimeTrustAuditAction = "created" | "replaced" | "reactivated" | "revoked"; +export type RuntimeTrustAuditAction = + | "created" + | "replaced" + | "reactivated" + | "revoked"; -export type RuntimeTrustAuditEntry = { action: RuntimeTrustAuditAction, actor_account_id: string, old_fingerprint?: string | null, new_fingerprint?: string | null, revision: number, at: string, }; +export type RuntimeTrustAuditEntry = { + action: RuntimeTrustAuditAction; + actor_account_id: string; + old_fingerprint?: string | null; + new_fingerprint?: string | null; + revision: number; + at: string; +}; -export type WorkspaceRuntimeDetail = { workspace_id: string, runtime: WorkspaceRuntimeResource, endpoint?: string | null, trust_key: RuntimeTrustKeyState, recent_audit: Array, }; +export type WorkspaceRuntimeDetail = { + workspace_id: string; + runtime: WorkspaceRuntimeResource; + endpoint?: string | null; + trust_key: RuntimeTrustKeyState; + recent_audit: Array; +}; -export type RuntimeTrustKeyRevealResponse = { public_key: string, }; +export type RuntimeTrustKeyRevealResponse = { public_key: string }; -export type RevokeRuntimeTrustKeyRequest = { expected_revision: number, }; +export type RevokeRuntimeTrustKeyRequest = { expected_revision: number }; -export type RemoveRuntimeRequest = { operation_id: string, expected_binding_revision: number, }; +export type RemoveRuntimeRequest = { + operation_id: string; + expected_binding_revision: number; +}; -export type RuntimeRemovalOperationState = "pending" | "cleanup_pending" | "succeeded" | "failed"; +export type RuntimeRemovalOperationState = + | "pending" + | "cleanup_pending" + | "succeeded" + | "failed"; -export type RuntimeRemovalOperationResponse = { operation_id: string, workspace_id: string, runtime_id: string, state: RuntimeRemovalOperationState, binding_removed: boolean, runtime_registration_removed: boolean | null, failure_category?: string | null, created_at: string, updated_at: string, completed_at?: string | null, }; +export type RuntimeRemovalOperationResponse = { + operation_id: string; + workspace_id: string; + runtime_id: string; + state: RuntimeRemovalOperationState; + binding_removed: boolean; + runtime_registration_removed: boolean | null; + failure_category?: string | null; + created_at: string; + updated_at: string; + completed_at?: string | null; +}; export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use"; -export type RuntimeTrustConflictResponse = { error: RuntimeTrustConflictKind, message: string, current_revision?: number, current_fingerprint?: string | null, }; +export type RuntimeTrustConflictResponse = { + error: RuntimeTrustConflictKind; + message: string; + current_revision?: number; + current_fingerprint?: string | null; +}; -export type RuntimePublicIdentityBundle = { identity_id: string, public_key: string, }; +export type RuntimePublicIdentityBundle = { + identity_id: string; + public_key: string; +}; -export type CreateRemoteRuntimeRequest = { public_bundle: RuntimePublicIdentityBundle, display_name?: string | null, endpoint: string, expected_revision?: number | null, }; +export type CreateRemoteRuntimeRequest = { + public_bundle: RuntimePublicIdentityBundle; + display_name?: string | null; + endpoint: string; + expected_revision?: number | null; +}; -export type UpdateRemoteRuntimeRequest = { display_name?: string | null, endpoint: string, }; +export type UpdateRemoteRuntimeRequest = { + display_name?: string | null; + endpoint: string; +}; export type RuntimeConnectionTestStatus = "compatible" | "failed"; -export type RuntimeConnectionTestFailureKind = "authentication" | "authorization" | "network_unreachable" | "timeout" | "tls_or_transport" | "malformed_response" | "protocol_version_mismatch" | "runtime_identity_mismatch" | "configuration"; +export type RuntimeConnectionTestFailureKind = + | "authentication" + | "authorization" + | "network_unreachable" + | "timeout" + | "tls_or_transport" + | "malformed_response" + | "protocol_version_mismatch" + | "runtime_identity_mismatch" + | "configuration"; -export type RuntimeConnectionTestResponse = { workspace_id: string, runtime_id: string, binding_revision: number, connection_state: RuntimeConnectionDisplayState, verification: RuntimeVerificationEvidenceSummary | null, checked_at: string, status: RuntimeConnectionTestStatus, failure_kind: RuntimeConnectionTestFailureKind | null, expected_protocol_version: number, actual_protocol_version: number | null, diagnostics: Array, }; +export type RuntimeConnectionTestResponse = { + workspace_id: string; + runtime_id: string; + binding_revision: number; + connection_state: RuntimeConnectionDisplayState; + verification: RuntimeVerificationEvidenceSummary | null; + checked_at: string; + status: RuntimeConnectionTestStatus; + failure_kind: RuntimeConnectionTestFailureKind | null; + expected_protocol_version: number; + actual_protocol_version: number | null; + diagnostics: Array; +}; diff --git a/web/workspace/src/lib/generated/memory-api.ts b/web/workspace/src/lib/generated/memory-api.ts index d1fd56c6d..c01a71858 100644 --- a/web/workspace/src/lib/generated/memory-api.ts +++ b/web/workspace/src/lib/generated/memory-api.ts @@ -1,26 +1,108 @@ // Generated from server-api. Do not edit by hand. // Regenerate: cargo run -q -p server-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts +export const MEMORY_API_LIMITS = { + maxResponseBytes: 8388608, + maxDocumentBytes: 4194304, + maxCollectionItems: 500, + maxStringBytes: 1048576, + maxIdentifierBytes: 512, +} as const; + export type DiagnosticSeverity = "info" | "warning" | "error"; -export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, }; +export type Diagnostic = { + code: string; + severity: DiagnosticSeverity; + message: string; +}; -export type MemoryDocumentResponse = { body_md: string, created_at: string, updated_at: string, bytes: number, record_source: string, }; +export type MemoryDocumentResponse = { + body_md: string; + created_at: string; + updated_at: string; + bytes: number; + record_source: string; +}; -export type MemoryCandidateKind = "preference" | "working_assumption" | "constraint" | "decision" | "open_question" | "lesson"; +export type MemoryCandidateKind = + | "preference" + | "working_assumption" + | "constraint" + | "decision" + | "open_question" + | "lesson"; -export type MemoryEvidenceOriginKind = "human_input" | "worker_input" | "flow_instruction" | "backend_instruction" | "model_output" | "tool_output" | "derived_summary" | "legacy_unknown"; +export type MemoryEvidenceOriginKind = + | "human_input" + | "worker_input" + | "flow_instruction" + | "backend_instruction" + | "model_output" + | "tool_output" + | "derived_summary" + | "legacy_unknown"; -export type MemoryEvidenceOrigin = { kind: MemoryEvidenceOriginKind, account_id?: string | null, workspace_id?: string | null, runtime_id?: string | null, worker_id?: string | null, flow_selector?: string | null, flow_definition_id?: string | null, flow_definition_revision?: number | null, }; +export type MemoryEvidenceOrigin = { + kind: MemoryEvidenceOriginKind; + account_id?: string | null; + workspace_id?: string | null; + runtime_id?: string | null; + worker_id?: string | null; + flow_selector?: string | null; + flow_definition_id?: string | null; + flow_definition_revision?: number | null; +}; -export type MemorySourceRef = { segment_id: string, range: [number, number], }; +export type MemorySourceRef = { segment_id: string; range: [number, number] }; -export type MemoryStagingEvidence = { id: string, kind: string, entry_range: [number, number] | null, origin?: MemoryEvidenceOrigin | null, excerpt: string | null, summary: string | null, }; +export type MemoryStagingEvidence = { + id: string; + kind: string; + entry_range: [number, number] | null; + origin?: MemoryEvidenceOrigin | null; + excerpt: string | null; + summary: string | null; +}; -export type MemorySourceEvidenceRef = { session_id: string | null, segment_id: string | null, entry_range: [number, number] | null, evidence_id: string | null, origin?: MemoryEvidenceOrigin | null, evidence_kind: string | null, label: string | null, summary: string | null, }; +export type MemorySourceEvidenceRef = { + session_id: string | null; + segment_id: string | null; + entry_range: [number, number] | null; + evidence_id: string | null; + origin?: MemoryEvidenceOrigin | null; + evidence_kind: string | null; + label: string | null; + summary: string | null; +}; -export type MemoryStagingRecord = { schema_version: number, id: string, extract_run_id: string, source: MemorySourceRef, kind: MemoryCandidateKind, claim: string, why_useful: string, staleness: string | null, evidence: Array, source_refs: Array, }; +export type MemoryStagingRecord = { + schema_version: number; + id: string; + extract_run_id: string; + source: MemorySourceRef; + kind: MemoryCandidateKind; + claim: string; + why_useful: string; + staleness: string | null; + evidence: Array; + source_refs: Array; +}; -export type MemoryStagingEntry = { id: string, byte_len: number, record: MemoryStagingRecord, }; +export type MemoryStagingEntry = { + id: string; + byte_len: number; + record: MemoryStagingRecord; +}; -export type MemoryStagingListResponse = { limit: number, returned_count: number, total_valid_count: number, invalid_count: number, truncated: boolean, order: string, record_authority: string, items: Array, diagnostics: Array, }; +export type MemoryStagingListResponse = { + limit: number; + returned_count: number; + total_valid_count: number; + invalid_count: number; + truncated: boolean; + order: string; + record_authority: string; + items: Array; + diagnostics: Array; +}; diff --git a/web/workspace/src/lib/generated/repository-access-api.ts b/web/workspace/src/lib/generated/repository-access-api.ts index 2d0eb9d07..f84c9200e 100644 --- a/web/workspace/src/lib/generated/repository-access-api.ts +++ b/web/workspace/src/lib/generated/repository-access-api.ts @@ -1,26 +1,93 @@ // Generated from server-api. Do not edit by hand. // Regenerate: cargo run -q -p server-api --features typescript --example generate_repository_access_types > web/workspace/src/lib/generated/repository-access-api.ts -export type RepositorySshCredential = { credential_id: string, workspace_id: string, name: string, public_key_algorithm: string, public_key_fingerprint: string, current_revision: number, status: string, created_at: string, rotated_at: string | null, referenced_repositories: Array, }; +export type RepositorySshCredential = { + credential_id: string; + workspace_id: string; + name: string; + public_key_algorithm: string; + public_key_fingerprint: string; + current_revision: number; + status: string; + created_at: string; + rotated_at: string | null; + referenced_repositories: Array; +}; -export type CreateRepositorySshCredentialRequest = { operation_id: string, credential_id: string, name: string, private_key: string, passphrase: string | null, }; +export type CreateRepositorySshCredentialRequest = { + operation_id: string; + credential_id: string; + name: string; + private_key: string; + passphrase: string | null; +}; -export type GenerateRepositorySshCredentialRequest = { operation_id: string, credential_id: string, name: string, }; +export type GenerateRepositorySshCredentialRequest = { + operation_id: string; + credential_id: string; + name: string; +}; -export type RepositorySshPublicKey = { credential_id: string, current_revision: number, public_key_algorithm: string, public_key_fingerprint: string, public_key: string, }; +export type RepositorySshPublicKey = { + credential_id: string; + current_revision: number; + public_key_algorithm: string; + public_key_fingerprint: string; + public_key: string; +}; -export type RotateRepositorySshCredentialRequest = { operation_id: string, expected_revision: number, private_key: string, passphrase: string | null, }; +export type RotateRepositorySshCredentialRequest = { + operation_id: string; + expected_revision: number; + private_key: string; + passphrase: string | null; +}; -export type DeleteRepositorySshCredentialRequest = { operation_id: string, expected_revision: number, }; +export type DeleteRepositorySshCredentialRequest = { + operation_id: string; + expected_revision: number; +}; -export type RepositorySshHostTrust = { host_trust_id: string, workspace_id: string, hostname: string, port: number, key_algorithm: string, host_key: string, fingerprint: string, current_revision: number, created_at: string, updated_at: string, referenced_repositories: Array, }; +export type RepositorySshHostTrust = { + host_trust_id: string; + workspace_id: string; + hostname: string; + port: number; + key_algorithm: string; + host_key: string; + fingerprint: string; + current_revision: number; + created_at: string; + updated_at: string; + referenced_repositories: Array; +}; -export type PutRepositorySshHostTrustRequest = { operation_id: string, host_trust_id: string, hostname: string, port: number, host_key: string, expected_revision: number | null, }; +export type PutRepositorySshHostTrustRequest = { + operation_id: string; + host_trust_id: string; + hostname: string; + port: number; + host_key: string; + expected_revision: number | null; +}; -export type DeleteRepositorySshHostTrustRequest = { operation_id: string, expected_revision: number, }; +export type DeleteRepositorySshHostTrustRequest = { + operation_id: string; + expected_revision: number; +}; export type RepositoryAccessMode = "read_only" | "read_write"; -export type RepositorySshAccessBinding = { repository_key: string, credential_id: string, host_trust_id: string, access: RepositoryAccessMode, }; +export type RepositorySshAccessBinding = { + repository_key: string; + credential_id: string; + host_trust_id: string; + access: RepositoryAccessMode; +}; -export type RepositoryAccessProjection = { workspace_id: string, config_revision: number, projection_digest: string, bindings: Array, }; +export type RepositoryAccessProjection = { + workspace_id: string; + config_revision: number; + projection_digest: string; + bindings: Array; +}; diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index 93cb65c48..05261c9bb 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:b478a5e12b43d9ac1261ba31358a5b80bb31e398bd57eccbd797f5e92c6f48b7) +// Input contract: 0.1.0 (sha256:386c56c830b49766e9f523f08c5bd4e24de490ba18c99c3d04063021d6b33352) // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Output: web/workspace/src/lib/generated/repository-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check diff --git a/web/workspace/src/lib/generated/skill-api.ts b/web/workspace/src/lib/generated/skill-api.ts index 3f75a4e86..f73ae0c95 100644 --- a/web/workspace/src/lib/generated/skill-api.ts +++ b/web/workspace/src/lib/generated/skill-api.ts @@ -20,22 +20,76 @@ export const SKILL_API_LIMITS = { export type SkillDiagnosticSeverity = "error" | "warning"; -export type SkillDiagnostic = { severity: SkillDiagnosticSeverity, code: string, message: string, source?: string, }; +export type SkillDiagnostic = { + severity: SkillDiagnosticSeverity; + code: string; + message: string; + source?: string; +}; export type SkillSourceKind = "builtin" | "workspace"; -export type SkillProvenance = { kind: SkillSourceKind, id: string, virtual_path?: string, revision?: number, source_digest?: string, tree_digest?: string, }; +export type SkillProvenance = { + kind: SkillSourceKind; + id: string; + virtual_path?: string; + revision?: number; + source_digest?: string; + tree_digest?: string; +}; export type SkillActivationStatus = "active" | "inactive"; export type SkillProjectionStatus = "valid" | "invalid"; -export type SkillProjectionIdentity = { config_revision: number, tree_digest: string, }; +export type SkillProjectionIdentity = { + config_revision: number; + tree_digest: string; +}; -export type SkillResourceRef = { kind: string, name: string, supported: boolean, diagnostic?: string, }; +export type SkillResourceRef = { + kind: string; + name: string; + supported: boolean; + diagnostic?: string; +}; -export type SkillCatalogEntry = { name: string, description: string, activation_status: SkillActivationStatus, projection_status: SkillProjectionStatus, provenance: SkillProvenance, overrides: Array, diagnostics: Array, }; +export type SkillCatalogEntry = { + name: string; + description: string; + activation_status: SkillActivationStatus; + projection_status: SkillProjectionStatus; + provenance: SkillProvenance; + overrides: Array; + diagnostics: Array; +}; -export type SkillCatalogResponse = { authority: string, projection: SkillProjectionIdentity, entries: Array, diagnostics: Array, }; +export type SkillCatalogResponse = { + authority: string; + projection: SkillProjectionIdentity; + entries: Array; + diagnostics: Array; +}; -export type SkillDetailResponse = { authority: string, projection: SkillProjectionIdentity, name: string, description: string, provenance: SkillProvenance, overrides: Array, diagnostics: Array, activation_status: SkillActivationStatus, projection_status: SkillProjectionStatus, body: string, allowed_tools: Array, allowed_tools_status: string, resources: Array, }; +export type SkillDetailResponse = { + authority: string; + projection: SkillProjectionIdentity; + name: string; + description: string; + provenance: SkillProvenance; + overrides: Array; + diagnostics: Array; + activation_status: SkillActivationStatus; + projection_status: SkillProjectionStatus; + body: string; + allowed_tools: Array; + allowed_tools_status: string; + resources: Array; +}; + +export type SkillActivationResponse = { + name: string; + provenance: SkillProvenance; + diagnostics: Array; + body: string; +}; diff --git a/web/workspace/src/lib/workspace/api/repository-access-loader.ts b/web/workspace/src/lib/workspace/api/repository-access-loader.ts index 7908a8443..26c5384e5 100644 --- a/web/workspace/src/lib/workspace/api/repository-access-loader.ts +++ b/web/workspace/src/lib/workspace/api/repository-access-loader.ts @@ -1,5 +1,9 @@ import { error } from "@sveltejs/kit"; -import { RepositoryAccessSchemaError } from "./repository-access.ts"; +import { readBoundedJson } from "./http.ts"; +import { + REPOSITORY_ACCESS_MAX_RESPONSE_BYTES, + RepositoryAccessSchemaError, +} from "./repository-access.ts"; export async function loadRepositoryAccessJson( fetcher: typeof fetch, @@ -27,7 +31,10 @@ export async function loadRepositoryAccessJson( let payload: unknown; try { - payload = await response.json(); + payload = await readBoundedJson( + response, + REPOSITORY_ACCESS_MAX_RESPONSE_BYTES, + ); } catch { error(502, { message: "Repository Access returned an invalid JSON response.", diff --git a/web/workspace/src/lib/workspace/api/repository-access.ts b/web/workspace/src/lib/workspace/api/repository-access.ts index 6d78ed45d..e18c7b224 100644 --- a/web/workspace/src/lib/workspace/api/repository-access.ts +++ b/web/workspace/src/lib/workspace/api/repository-access.ts @@ -5,6 +5,10 @@ import type { RepositorySshPublicKey, } from "../../generated/repository-access-api.ts"; +export const REPOSITORY_ACCESS_MAX_RESPONSE_BYTES = 4 * 1024 * 1024; +const MAX_COLLECTION_ITEMS = 1000; +const MAX_STRING_BYTES = 1024 * 1024; + export class RepositoryAccessSchemaError extends Error { constructor(path: string, expected: string) { super( @@ -165,7 +169,7 @@ function readRecord( ); if (unknownKey !== undefined) { throw new RepositoryAccessSchemaError( - `${path}.${unknownKey}`, + `${path}.${unknownKey.slice(0, 128)}`, "no unknown field", ); } @@ -173,7 +177,7 @@ function readRecord( } function readArray(value: unknown, path: string): unknown[] { - if (!Array.isArray(value)) { + if (!Array.isArray(value) || value.length > MAX_COLLECTION_ITEMS) { throw new RepositoryAccessSchemaError(path, "an array"); } return value; @@ -185,8 +189,11 @@ function readString( path: string, ): string { const value = record[key]; - if (typeof value !== "string") { - throw new RepositoryAccessSchemaError(`${path}.${key}`, "a string"); + if ( + typeof value !== "string" || + new TextEncoder().encode(value).byteLength > MAX_STRING_BYTES + ) { + throw new RepositoryAccessSchemaError(`${path}.${key}`, "a bounded string"); } return value; } @@ -197,8 +204,15 @@ function readNullableString( path: string, ): string | null { const value = record[key]; - if (value !== null && typeof value !== "string") { - throw new RepositoryAccessSchemaError(`${path}.${key}`, "a string or null"); + if ( + value !== null && + (typeof value !== "string" || + new TextEncoder().encode(value).byteLength > MAX_STRING_BYTES) + ) { + throw new RepositoryAccessSchemaError( + `${path}.${key}`, + "a bounded string or null", + ); } return value; } @@ -210,10 +224,13 @@ function readStringArray( ): string[] { const values = readArray(record[key], `${path}.${key}`); values.forEach((value, index) => { - if (typeof value !== "string") { + if ( + typeof value !== "string" || + new TextEncoder().encode(value).byteLength > MAX_STRING_BYTES + ) { throw new RepositoryAccessSchemaError( `${path}.${key}[${index}]`, - "a string", + "a bounded string", ); } }); diff --git a/web/workspace/src/lib/workspace/api/workspace-model.ts b/web/workspace/src/lib/workspace/api/workspace-model.ts index 0665f423b..6449d53b3 100644 --- a/web/workspace/src/lib/workspace/api/workspace-model.ts +++ b/web/workspace/src/lib/workspace/api/workspace-model.ts @@ -395,6 +395,27 @@ function workspaceRepositoryRecord( }; } +function workspaceString(value: unknown, path: string): string { + const parsed = string(value, path); + if (parsed.length > REPOSITORY_API_LIMITS.maxStringCodeUnits) { + throw new Error(`${path} exceeds the Workspace API string limit`); + } + return parsed; +} + +function workspaceDiagnostics(value: unknown, path: string): Diagnostic[] { + const entries = array(value, path); + if (entries.length > 100) { + throw new Error(`${path} exceeds the Workspace API collection limit`); + } + return entries.map((entry, index) => { + const parsed = diagnostic(entry, `${path}[${index}]`); + workspaceString(parsed.code, `${path}[${index}].code`); + workspaceString(parsed.message, `${path}[${index}].message`); + return parsed; + }); +} + function extensionPoint( value: unknown, path: string, @@ -402,12 +423,9 @@ function extensionPoint( const item = object(value, path); exactKeys(item, ["status", "note", "diagnostics"], path); return { - status: string(item.status, `${path}.status`), - note: string(item.note, `${path}.note`), - diagnostics: array(item.diagnostics, `${path}.diagnostics`).map(( - entry, - index, - ) => diagnostic(entry, `${path}.diagnostics[${index}]`)), + status: workspaceString(item.status, `${path}.status`), + note: workspaceString(item.note, `${path}.note`), + diagnostics: workspaceDiagnostics(item.diagnostics, `${path}.diagnostics`), }; } @@ -423,7 +441,7 @@ function extensionPoints( "companion_console", ], path); return { - store: string(item.store, `${path}.store`), + store: workspaceString(item.store, `${path}.store`), event_stream: extensionPoint(item.event_stream, `${path}.event_stream`), host_worker_bridge: extensionPoint( item.host_worker_bridge, @@ -447,13 +465,16 @@ function authConfig(value: unknown, path: string): WorkspaceAuthConfig { ); return { Passkey: { - rp_id: string(passkey.rp_id, `${path}.Passkey.rp_id`), - origin: string(passkey.origin, `${path}.Passkey.origin`), - public_base_url: string( + rp_id: workspaceString(passkey.rp_id, `${path}.Passkey.rp_id`), + origin: workspaceString(passkey.origin, `${path}.Passkey.origin`), + public_base_url: workspaceString( passkey.public_base_url, `${path}.Passkey.public_base_url`, ), - cookie_name: string(passkey.cookie_name, `${path}.Passkey.cookie_name`), + cookie_name: workspaceString( + passkey.cookie_name, + `${path}.Passkey.cookie_name`, + ), }, }; } @@ -575,23 +596,27 @@ export function parseWorkspaceResponse(value: unknown): WorkspaceResponse { ], "workspace response", ); + const schemaVersion = integer( + response.schema_version, + "workspace response.schema_version", + ); + if (schemaVersion < 0) { + throw new Error("workspace response.schema_version must be non-negative"); + } return { - workspace_id: string( + workspace_id: workspaceString( response.workspace_id, "workspace response.workspace_id", ), - display_name: string( + display_name: workspaceString( response.display_name, "workspace response.display_name", ), - record_authority: string( + record_authority: workspaceString( response.record_authority, "workspace response.record_authority", ), - schema_version: integer( - response.schema_version, - "workspace response.schema_version", - ), + schema_version: schemaVersion, auth: authConfig(response.auth, "workspace response.auth"), permissions: permissions( response.permissions, diff --git a/web/workspace/src/lib/workspace/config-source/api.ts b/web/workspace/src/lib/workspace/config-source/api.ts index dfa09dfaa..479ba0b9b 100644 --- a/web/workspace/src/lib/workspace/config-source/api.ts +++ b/web/workspace/src/lib/workspace/config-source/api.ts @@ -1,20 +1,267 @@ import type { ConfigCommitRequest, + ConfigContentType, ConfigEntry, + ConfigProjectionValidator, + ConfigSchemaContribution, ConfigTreeSnapshot, + ToolchainContract, + WorkspaceConfigSchemaBundle, WorkspaceConfigTreeResponse, } from "./types.ts"; +const MAX_RESPONSE_BYTES = 8 * 1024 * 1024; +const MAX_ERROR_BYTES = 4 * 1024; +const MAX_ENTRY_COUNT = 256; +const MAX_ENTRY_BYTES = 256 * 1024; +const MAX_PATH_BYTES = 512; +const MAX_STRING_BYTES = 4 * 1024; +const MAX_DIGEST_BYTES = 256; +const MAX_COLLECTION_ITEMS = 256; + +export class ConfigSourceApiError extends Error { + constructor( + message: string, + readonly status: number, + ) { + super(message); + this.name = "ConfigSourceApiError"; + } +} + +type JsonRecord = Record; + +type Parser = (value: unknown) => T; + function sourceTreeUrl(workspaceId: string): string { return `/api/w/${encodeURIComponent(workspaceId)}/config/source-tree`; } -async function readJson(response: Response): Promise { - if (!response.ok) { - const body = await response.text(); - throw new Error(body || `${response.status} ${response.statusText}`); +async function readJson( + response: Response, + parser: Parser, +): Promise { + const limit = response.ok ? MAX_RESPONSE_BYTES : MAX_ERROR_BYTES; + const declared = response.headers.get("content-length"); + if (declared !== null) { + const bytes = Number(declared); + if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > limit) { + throw new ConfigSourceApiError( + response.ok + ? "Workspace configuration response is too large." + : "Workspace configuration request failed.", + response.ok ? 502 : response.status, + ); + } } - return await response.json() as T; + const bytes = new Uint8Array(await response.arrayBuffer()); + if (bytes.byteLength > limit) { + throw new ConfigSourceApiError( + response.ok + ? "Workspace configuration response is too large." + : "Workspace configuration request failed.", + response.ok ? 502 : response.status, + ); + } + const text = new TextDecoder().decode(bytes); + if (!response.ok) { + throw new ConfigSourceApiError( + text || `${response.status} ${response.statusText}`, + response.status, + ); + } + let value: unknown; + try { + value = JSON.parse(text) as unknown; + } catch { + throw schemaError(); + } + return parser(value); +} + +function schemaError(): ConfigSourceApiError { + return new ConfigSourceApiError( + "Workspace configuration returned an invalid response.", + 502, + ); +} + +function objectValue(value: unknown): JsonRecord { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw schemaError(); + } + return value as JsonRecord; +} + +function record( + value: unknown, + required: readonly string[], + optional: readonly string[] = [], +): JsonRecord { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw schemaError(); + } + const result = value as JsonRecord; + const allowed = new Set([...required, ...optional]); + if ( + required.some((key) => !(key in result)) || + Object.keys(result).some((key) => !allowed.has(key)) + ) { + throw schemaError(); + } + return result; +} + +function boundedString(value: unknown, maxBytes = MAX_STRING_BYTES): string { + if ( + typeof value !== "string" || + new TextEncoder().encode(value).byteLength > maxBytes + ) { + throw schemaError(); + } + return value; +} + +function safeInteger(value: unknown): number { + if (!Number.isSafeInteger(value) || (value as number) < 0) { + throw schemaError(); + } + return value as number; +} + +function boundedArray(value: unknown): unknown[] { + if (!Array.isArray(value) || value.length > MAX_COLLECTION_ITEMS) { + throw schemaError(); + } + return value; +} + +function parseContentType(value: unknown): ConfigContentType { + if (value !== "decodal" && value !== "text") throw schemaError(); + return value; +} + +export function parseConfigEntry(value: unknown): ConfigEntry { + const item = record(value, [ + "path", + "content_type", + "content", + "content_digest", + ]); + return { + path: boundedString(item.path, MAX_PATH_BYTES), + content_type: parseContentType(item.content_type), + content: boundedString(item.content, MAX_ENTRY_BYTES), + content_digest: boundedString(item.content_digest, MAX_DIGEST_BYTES), + }; +} + +function parseStringMap(value: unknown): Record { + const item = objectValue(value); + const entries = Object.entries(item); + if (entries.length > MAX_COLLECTION_ITEMS) throw schemaError(); + const result: Record = {}; + for (const [key, entry] of entries) { + const boundedKey = boundedString(key, MAX_PATH_BYTES); + result[boundedKey] = boundedString(entry, MAX_PATH_BYTES); + } + return result; +} + +function parseProjectionValidator(value: unknown): ConfigProjectionValidator { + const item = record(value, ["kind", "namespace"], ["key_aliases"]); + if (item.kind !== "static_template_catalog") throw schemaError(); + return { + kind: "static_template_catalog", + namespace: boundedString(item.namespace, MAX_PATH_BYTES), + key_aliases: item.key_aliases === undefined + ? {} + : parseStringMap(item.key_aliases), + }; +} + +function parseContribution(value: unknown): ConfigSchemaContribution { + const item = record( + value, + ["provider_id", "namespace", "version", "source", "source_digest"], + ["projection_validator"], + ); + const projection = item.projection_validator === undefined + ? undefined + : parseProjectionValidator(item.projection_validator); + return { + provider_id: boundedString(item.provider_id), + namespace: boundedString(item.namespace), + version: boundedString(item.version), + source: boundedString(item.source), + ...(projection === undefined ? {} : { projection_validator: projection }), + source_digest: boundedString(item.source_digest, MAX_DIGEST_BYTES), + }; +} + +function parseSchemaBundle(value: unknown): WorkspaceConfigSchemaBundle { + const item = record(value, ["contributions", "source", "fingerprint"]); + return { + contributions: boundedArray(item.contributions).map(parseContribution), + source: boundedString(item.source), + fingerprint: boundedString(item.fingerprint, MAX_DIGEST_BYTES), + }; +} + +function parseToolchainContract(value: unknown): ToolchainContract { + const item = record(value, [ + "contract_version", + "decodal_version", + "schema_version", + "entrypoints", + "import_policy_version", + "schema_bundle", + "fingerprint", + ]); + return { + contract_version: safeInteger(item.contract_version), + decodal_version: boundedString(item.decodal_version), + schema_version: safeInteger(item.schema_version), + entrypoints: boundedArray(item.entrypoints).map((entry) => + boundedString(entry, MAX_PATH_BYTES) + ), + import_policy_version: safeInteger(item.import_policy_version), + schema_bundle: parseSchemaBundle(item.schema_bundle), + fingerprint: boundedString(item.fingerprint, MAX_DIGEST_BYTES), + }; +} + +export function parseConfigTreeSnapshot(value: unknown): ConfigTreeSnapshot { + const item = record(value, ["revision", "digest", "entries"]); + const entriesRecord = objectValue(item.entries); + const entries = Object.entries(entriesRecord); + if (entries.length > MAX_ENTRY_COUNT) throw schemaError(); + const parsedEntries: Record = {}; + for (const [path, entry] of entries) { + const boundedPath = boundedString(path, MAX_PATH_BYTES); + const parsed = parseConfigEntry(entry); + if (parsed.path !== boundedPath) throw schemaError(); + parsedEntries[boundedPath] = parsed; + } + return { + revision: safeInteger(item.revision), + digest: boundedString(item.digest, MAX_DIGEST_BYTES), + entries: parsedEntries, + }; +} + +export function parseWorkspaceConfigTreeResponse( + value: unknown, +): WorkspaceConfigTreeResponse { + const item = record(value, ["snapshot", "contract", "projection_digest"]); + return { + snapshot: parseConfigTreeSnapshot(item.snapshot), + contract: parseToolchainContract(item.contract), + projection_digest: boundedString( + item.projection_digest, + MAX_DIGEST_BYTES, + ), + }; } export async function fetchConfigTree( @@ -25,6 +272,7 @@ export async function fetchConfigTree( await fetcher(sourceTreeUrl(workspaceId), { headers: { accept: "application/json" }, }), + parseWorkspaceConfigTreeResponse, ); } @@ -38,6 +286,7 @@ export async function fetchConfigEntry( `${sourceTreeUrl(workspaceId)}/entries/${encodeURIComponent(path)}`, { headers: { accept: "application/json" } }, ), + parseConfigEntry, ); } @@ -50,6 +299,7 @@ export async function fetchConfigRevision( await fetcher(`${sourceTreeUrl(workspaceId)}/revisions/${revision}`, { headers: { accept: "application/json" }, }), + parseConfigTreeSnapshot, ); } @@ -64,5 +314,6 @@ export async function commitConfigTree( headers: { "content-type": "application/json" }, body: JSON.stringify(request), }), + parseWorkspaceConfigTreeResponse, ); } diff --git a/web/workspace/src/lib/workspace/config-source/types.ts b/web/workspace/src/lib/workspace/config-source/types.ts index 73e44afe9..4559c1960 100644 --- a/web/workspace/src/lib/workspace/config-source/types.ts +++ b/web/workspace/src/lib/workspace/config-source/types.ts @@ -1,16 +1,21 @@ -// Core and HTTP DTOs are generated from Rust with ts-rs. -export type { ConfigContentType } from "./generated/types/ConfigContentType.ts"; +// Workspace REST DTOs are generated from the server-api Rust authority. +export type { + ConfigCommitRequest, + ConfigContentType, + ConfigEntry, + ConfigProjectionValidator, + ConfigSchemaContribution, + ConfigTreeChange, + ConfigTreeSnapshot, + ToolchainContract, + WorkspaceConfigSchemaBundle, + WorkspaceConfigTreeResponse, +} from "$lib/generated/legacy-server-api.ts"; + +// Browser-local Decodal/WASM projections remain owned by config-source. export type { ConfigDiagnostic } from "./generated/types/ConfigDiagnostic.ts"; export type { ConfigDiagnosticLabel } from "./generated/types/ConfigDiagnosticLabel.ts"; -export type { ConfigEntry } from "./generated/types/ConfigEntry.ts"; export type { ConfigSpan } from "./generated/types/ConfigSpan.ts"; -export type { ConfigTreeChange } from "./generated/types/ConfigTreeChange.ts"; -export type { ConfigSchemaContribution } from "./generated/types/ConfigSchemaContribution.ts"; -export type { WorkspaceConfigSchemaBundle } from "./generated/types/WorkspaceConfigSchemaBundle.ts"; -export type { ConfigTreeSnapshot } from "./generated/types/ConfigTreeSnapshot.ts"; export type { EvaluatedProjection } from "./generated/types/EvaluatedProjection.ts"; export type { EvaluationResult } from "./generated/types/EvaluationResult.ts"; -export type { ToolchainContract } from "./generated/types/ToolchainContract.ts"; export type { VirtualPath } from "./generated/types/VirtualPath.ts"; -export type { ConfigCommitRequest } from "./generated/types/ConfigCommitRequest.ts"; -export type { WorkspaceConfigState as WorkspaceConfigTreeResponse } from "./generated/types/WorkspaceConfigState.ts"; diff --git a/web/workspace/src/lib/workspace/memory/api.ts b/web/workspace/src/lib/workspace/memory/api.ts index 5a209740d..cae6a9c32 100644 --- a/web/workspace/src/lib/workspace/memory/api.ts +++ b/web/workspace/src/lib/workspace/memory/api.ts @@ -1,3 +1,4 @@ +import { MEMORY_API_LIMITS } from "$lib/generated/memory-api"; import type { Diagnostic, DiagnosticSeverity, @@ -13,10 +14,10 @@ import type { MemoryStagingRecord, } from "$lib/generated/memory-api"; -const MAX_STAGING_ITEMS = 500; -const MAX_EVIDENCE_PER_RECORD = 500; -const MAX_SOURCE_REFS_PER_RECORD = 500; -const MAX_ORIGIN_VALUE_LENGTH = 512; +const MAX_STAGING_ITEMS = MEMORY_API_LIMITS.maxCollectionItems; +const MAX_EVIDENCE_PER_RECORD = MEMORY_API_LIMITS.maxCollectionItems; +const MAX_SOURCE_REFS_PER_RECORD = MEMORY_API_LIMITS.maxCollectionItems; +const MAX_ORIGIN_VALUE_LENGTH = MEMORY_API_LIMITS.maxIdentifierBytes; const candidateKinds = new Set([ "preference", @@ -51,7 +52,11 @@ export function parseMemoryDocumentResponse( "Memory document response", ); return { - body_md: requiredString(record, "body_md"), + body_md: requiredString( + record, + "body_md", + MEMORY_API_LIMITS.maxDocumentBytes, + ), created_at: requiredString(record, "created_at"), updated_at: requiredString(record, "updated_at"), bytes: requiredNonNegativeInteger(record, "bytes"), @@ -258,7 +263,10 @@ function parseEvidenceOrigin(value: unknown): MemoryEvidenceOrigin { ) { if (key in record) { const text = nullableString(record, key); - if (text !== null && text.length > MAX_ORIGIN_VALUE_LENGTH) { + if ( + text !== null && + new TextEncoder().encode(text).byteLength > MAX_ORIGIN_VALUE_LENGTH + ) { invalid(`${key} exceeds the Memory origin limit`); } result[key] = text; @@ -328,11 +336,19 @@ function boundedArray( return value; } -function requiredString(record: Record, key: string): string { - if (typeof record[key] !== "string") { - invalid(`${key} must be a string`); +function requiredString( + record: Record, + key: string, + maxBytes: number = MEMORY_API_LIMITS.maxStringBytes, +): string { + const value = record[key]; + if ( + typeof value !== "string" || + new TextEncoder().encode(value).byteLength > maxBytes + ) { + invalid(`${key} must be a bounded string`); } - return record[key]; + return value; } function nullableString( @@ -340,8 +356,13 @@ function nullableString( key: string, ): string | null { const value = record[key]; - if (value !== null && typeof value !== "string") { - invalid(`${key} must be a string or null`); + if ( + value !== null && + (typeof value !== "string" || + new TextEncoder().encode(value).byteLength > + MEMORY_API_LIMITS.maxStringBytes) + ) { + invalid(`${key} must be a bounded string or null`); } return value; } diff --git a/web/workspace/src/lib/workspace/settings/profile-api.ts b/web/workspace/src/lib/workspace/settings/profile-api.ts index 53383cc6c..88d1b0c3e 100644 --- a/web/workspace/src/lib/workspace/settings/profile-api.ts +++ b/web/workspace/src/lib/workspace/settings/profile-api.ts @@ -13,6 +13,12 @@ import type { WorkspaceSigningIdentityResponse, WorkspaceSigningIdentityState, } from "$lib/generated/legacy-server-api"; +import { readBoundedJson } from "$lib/workspace/api/http.ts"; + +const MAX_PROFILE_RESPONSE_BYTES = 4 * 1024 * 1024; +const MAX_PROFILE_ERROR_BYTES = 4 * 1024; +const MAX_PROFILE_STRING_BYTES = 1024 * 1024; +const MAX_PROFILE_COLLECTION_ITEMS = 1000; export class ProfileApiError extends Error { constructor( @@ -49,10 +55,7 @@ function exactKeys( } function stringValue(value: unknown, context: string): string { - if (typeof value !== "string") { - throw new ProfileApiError(`${context} returned an invalid response.`, 502); - } - return value; + return boundedStringValue(value, context, MAX_PROFILE_STRING_BYTES); } function boundedStringValue( @@ -60,7 +63,10 @@ function boundedStringValue( context: string, maxBytes: number, ): string { - const text = stringValue(value, context); + if (typeof value !== "string") { + throw new ProfileApiError(`${context} returned an invalid response.`, 502); + } + const text = value; if (new TextEncoder().encode(text).byteLength > maxBytes) { throw new ProfileApiError(`${context} returned an invalid response.`, 502); } @@ -107,7 +113,7 @@ function arrayValue( parser: (item: unknown) => T, context: string, ): T[] { - if (!Array.isArray(value)) { + if (!Array.isArray(value) || value.length > MAX_PROFILE_COLLECTION_ITEMS) { throw new ProfileApiError(`${context} returned an invalid response.`, 502); } return value.map(parser); @@ -502,17 +508,23 @@ export function parseWorkspaceSigningIdentityResponse( }; } +async function boundedErrorText(response: Response): Promise { + const bytes = new Uint8Array(await response.arrayBuffer()); + if (bytes.byteLength > MAX_PROFILE_ERROR_BYTES) return ""; + return new TextDecoder().decode(bytes); +} + async function parseResponse( response: Response, parser: (value: unknown) => T, ): Promise { if (!response.ok) { throw new ProfileApiError( - (await response.text()) || response.statusText, + (await boundedErrorText(response)) || response.statusText, response.status, ); } - return parser(await response.json() as unknown); + return parser(await readBoundedJson(response, MAX_PROFILE_RESPONSE_BYTES)); } export async function fetchWorkspaceMetadata( diff --git a/web/workspace/src/routes/w/[workspaceId]/+layout.ts b/web/workspace/src/routes/w/[workspaceId]/+layout.ts index 8f83bb8d1..5ffac59e3 100644 --- a/web/workspace/src/routes/w/[workspaceId]/+layout.ts +++ b/web/workspace/src/routes/w/[workspaceId]/+layout.ts @@ -7,21 +7,18 @@ import type { LayoutLoad } from "./$types"; export const load: LayoutLoad = async ({ fetch, params }) => { const workspaceId = params.workspaceId; const [workspaceResult, repositoryResult] = await Promise.all([ - loadJson(fetch, workspaceApiPath(workspaceId, "/workspace")), + loadJson( + fetch, + workspaceApiPath(workspaceId, "/workspace"), + undefined, + parseWorkspaceResponse, + { diagnosticLabel: "Workspace API", maxResponseBytes: 1024 * 1024 }, + ), loadWorkspaceRepositoryList(fetch, workspaceId), ]); - let workspace = null; - let workspaceError = workspaceResult.error; - if (workspaceResult.data !== null) { - try { - workspace = parseWorkspaceResponse(workspaceResult.data); - } catch (cause) { - workspaceError = cause instanceof Error - ? cause.message - : "invalid workspace response"; - } - } + const workspace = workspaceResult.data; + const workspaceError = workspaceResult.error; if (!workspace) { error(404, { message: workspaceError ?? `Workspace ${workspaceId} is unavailable`, diff --git a/web/workspace/src/routes/w/[workspaceId]/memory/+page.ts b/web/workspace/src/routes/w/[workspaceId]/memory/+page.ts index 9e25c8411..65205285c 100644 --- a/web/workspace/src/routes/w/[workspaceId]/memory/+page.ts +++ b/web/workspace/src/routes/w/[workspaceId]/memory/+page.ts @@ -1,3 +1,4 @@ +import { MEMORY_API_LIMITS } from "$lib/generated/memory-api"; import { loadJson, workspaceApiPath } from "$lib/workspace/api/http"; import { parseMemoryDocumentResponse } from "$lib/workspace/memory/api"; import type { PageLoad } from "./$types"; @@ -10,6 +11,10 @@ export const load: PageLoad = async ({ fetch, params }) => { workspaceApiPath(params.workspaceId, "/memory"), undefined, parseMemoryDocumentResponse, + { + diagnosticLabel: "Memory API", + maxResponseBytes: MEMORY_API_LIMITS.maxResponseBytes, + }, ), }; }; diff --git a/web/workspace/src/routes/w/[workspaceId]/memory/staging/+page.ts b/web/workspace/src/routes/w/[workspaceId]/memory/staging/+page.ts index 89a02e895..ecc56e866 100644 --- a/web/workspace/src/routes/w/[workspaceId]/memory/staging/+page.ts +++ b/web/workspace/src/routes/w/[workspaceId]/memory/staging/+page.ts @@ -1,3 +1,4 @@ +import { MEMORY_API_LIMITS } from "$lib/generated/memory-api"; import { loadJson, workspaceApiPath } from "$lib/workspace/api/http"; import { parseMemoryStagingListResponse } from "$lib/workspace/memory/api"; import type { PageLoad } from "./$types"; @@ -10,6 +11,10 @@ export const load: PageLoad = async ({ fetch, params }) => { `${workspaceApiPath(params.workspaceId, "/memory/staging")}?limit=200`, undefined, parseMemoryStagingListResponse, + { + diagnosticLabel: "Memory API", + maxResponseBytes: MEMORY_API_LIMITS.maxResponseBytes, + }, ), }; }; diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/repository-access/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/repository-access/+page.svelte index 867873886..bac097bc9 100644 --- a/web/workspace/src/routes/w/[workspaceId]/settings/repository-access/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/settings/repository-access/+page.svelte @@ -12,10 +12,12 @@ RotateRepositorySshCredentialRequest, } from '$lib/generated/repository-access-api'; import { + REPOSITORY_ACCESS_MAX_RESPONSE_BYTES, parseRepositorySshCredential, parseRepositorySshHostTrust, parseRepositorySshPublicKey, } from '$lib/workspace/api/repository-access'; + import { readBoundedJson } from '$lib/workspace/api/http'; import type { PageProps } from './$types'; let { data }: PageProps = $props(); @@ -71,13 +73,21 @@ body: JSON.stringify(body) }); if (!response.ok) { + await response.body?.cancel(); throw new Error(`Repository Access request failed with status ${response.status}.`); } - if (response.status === 204) return undefined; - const payload: unknown = await response.json(); if (parse === null) { - throw new Error('Repository Access returned an unexpected response body.'); + if (response.status !== 204) { + await response.body?.cancel(); + throw new Error('Repository Access returned an unexpected response body.'); + } + await response.body?.cancel(); + return undefined; } + if (response.status === 204) { + throw new Error('Repository Access returned an unexpected empty response.'); + } + const payload = await readBoundedJson(response, REPOSITORY_ACCESS_MAX_RESPONSE_BYTES); return parse(payload); } @@ -86,10 +96,11 @@ `${base}/credentials/${encodeURIComponent(credentialId)}/public-key`, { headers: { accept: 'application/json' } } ); - const body = await response.json(); if (!response.ok) { + await response.body?.cancel(); throw new Error(`Repository Access request failed with status ${response.status}.`); } + const body = await readBoundedJson(response, REPOSITORY_ACCESS_MAX_RESPONSE_BYTES); return parseRepositorySshPublicKey(body); } diff --git a/web/workspace/test/config-source/api.test.ts b/web/workspace/test/config-source/api.test.ts index e2008110b..6c5771099 100644 --- a/web/workspace/test/config-source/api.test.ts +++ b/web/workspace/test/config-source/api.test.ts @@ -1,11 +1,18 @@ /// -import { assert, assertEquals } from "jsr:@std/assert"; +import { + assert, + assertEquals, + assertRejects, + assertThrows, +} from "jsr:@std/assert"; import { commitConfigTree, + ConfigSourceApiError, fetchConfigEntry, fetchConfigRevision, fetchConfigTree, + parseWorkspaceConfigTreeResponse, } from "../../src/lib/workspace/config-source/api.ts"; function response(body: unknown, status = 200): Response { @@ -15,11 +22,43 @@ function response(body: unknown, status = 200): Response { }); } +const entry = { + path: "profiles/main.dcdl", + content_type: "decodal", + content: "profile = {}", + content_digest: "sha256:entry", +}; +const snapshot = { + revision: 7, + digest: "sha256:tree", + entries: { "profiles/main.dcdl": entry }, +}; +const tree = { + snapshot, + contract: { + contract_version: 1, + decodal_version: "0.4.0", + schema_version: 1, + entrypoints: ["main.dcdl"], + import_policy_version: 1, + schema_bundle: { + contributions: [], + source: "builtin", + fingerprint: "sha256:schema", + }, + fingerprint: "sha256:toolchain", + }, + projection_digest: "sha256:projection", +}; + Deno.test("config source API commits directly through the workspace scope", async () => { const calls: Array<{ url: string; init?: RequestInit }> = []; const fetcher = ((input: string | URL | Request, init?: RequestInit) => { - calls.push({ url: String(input), init }); - return Promise.resolve(response({ ok: true })); + const url = String(input); + calls.push({ url, init }); + if (url.includes("/entries/")) return Promise.resolve(response(entry)); + if (url.includes("/revisions/")) return Promise.resolve(response(snapshot)); + return Promise.resolve(response(tree)); }) as typeof fetch; await fetchConfigTree("w/one", fetcher); @@ -44,7 +83,63 @@ Deno.test("config source API commits directly through the workspace scope", asyn ); }); -Deno.test("config source API surfaces failed evaluation instead of treating it as a successful commit", async () => { +Deno.test("config source API rejects unknown response fields", async () => { + const fetcher = + (() => + Promise.resolve(response({ ...tree, unexpected: true }))) as typeof fetch; + await assertRejects( + () => fetchConfigTree("w", fetcher), + ConfigSourceApiError, + "invalid response", + ); +}); + +Deno.test("config source API rejects mismatched entry map paths", async () => { + const invalid = { + ...tree, + snapshot: { + ...snapshot, + entries: { "profiles/other.dcdl": entry }, + }, + }; + const fetcher = (() => Promise.resolve(response(invalid))) as typeof fetch; + await assertRejects( + () => fetchConfigTree("w", fetcher), + ConfigSourceApiError, + "invalid response", + ); +}); + +Deno.test("config source API rejects unsafe revisions and oversized entry content", () => { + assertThrows( + () => + parseWorkspaceConfigTreeResponse({ + ...tree, + snapshot: { ...snapshot, revision: Number.MAX_SAFE_INTEGER + 1 }, + }), + ConfigSourceApiError, + "invalid response", + ); + assertThrows( + () => + parseWorkspaceConfigTreeResponse({ + ...tree, + snapshot: { + ...snapshot, + entries: { + "profiles/main.dcdl": { + ...entry, + content: "x".repeat(256 * 1024 + 1), + }, + }, + }, + }), + ConfigSourceApiError, + "invalid response", + ); +}); + +Deno.test("config source API surfaces bounded failed evaluation details", async () => { const fetcher = (() => Promise.resolve( new Response("structured diagnostics", { status: 422 }), From 6d65d50ec121238e025e9b8ffd15e2e1cb87a67a Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 05:42:50 +0900 Subject: [PATCH 03/16] feat: migrate ticket and objective domains to ServerApi --- Cargo.lock | 4 + crates/client/src/backend_workspace.rs | 6 +- crates/client/src/workspace_product.rs | 498 +- crates/merge-request/Cargo.toml | 1 + crates/merge-request/src/lib.rs | 38 +- crates/server-api/Cargo.toml | 2 + crates/server-api/src/lib.rs | 1651 ++- crates/ticket/src/lib.rs | 128 +- crates/workspace-server/src/server.rs | 1464 ++- openapi/server-api.json | 10460 +++++++++++++++- .../src/lib/generated/repository-api.ts | 2 +- 11 files changed, 13759 insertions(+), 495 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31f95a8c1..3ad43461a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2579,6 +2579,7 @@ version = "0.1.0" dependencies = [ "chrono", "rusqlite", + "schemars", "serde", "serde_json", "tempfile", @@ -3910,6 +3911,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ + "chrono", "dyn-clone", "ref-cast", "schemars_derive", @@ -4150,12 +4152,14 @@ version = "0.1.0" dependencies = [ "api-macros", "memory", + "merge-request", "protocol", "runtime-api", "schemars", "serde", "serde_json", "sha2 0.11.0", + "ticket", "tokio", "ts-rs", "webauthn-rs-proto", diff --git a/crates/client/src/backend_workspace.rs b/crates/client/src/backend_workspace.rs index d7564e81f..35536fa23 100644 --- a/crates/client/src/backend_workspace.rs +++ b/crates/client/src/backend_workspace.rs @@ -68,7 +68,7 @@ impl From, ) -> BackendWorkspaceClientError { @@ -92,7 +92,7 @@ fn server_client_error( } #[derive(Clone, Debug)] -struct ServerBearerAuthorizer { +pub(crate) struct ServerBearerAuthorizer { authorization: String, } @@ -114,7 +114,7 @@ impl server_api::client_support::RequestAuthorizer for ServerBearerAuthorizer { const SERVER_RESPONSE_LIMIT: usize = 16 * 1024 * 1024; -fn server_api_client( +pub(crate) fn server_api_client( backend: &BackendApiClient, ) -> Result, BackendWorkspaceClientError> { server_api::ServerApiClient::builder(backend.origin().as_str()) diff --git a/crates/client/src/workspace_product.rs b/crates/client/src/workspace_product.rs index 5d42a8e22..e561b9a44 100644 --- a/crates/client/src/workspace_product.rs +++ b/crates/client/src/workspace_product.rs @@ -1,13 +1,12 @@ use reqwest::Method; +use serde::Serialize; use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; use server_api::{ BrowserCreateWorkerResponse, BrowserWorkspaceOrchestratorResponse, CreateWorkspaceWorkerRequest, ListResponse, MemoryDocumentResponse, MemoryStagingListResponse, ObjectiveCreateRequest, ObjectiveDetail, ObjectiveEditRequest, ObjectiveLinkTicketRequest, ObjectiveStateRequest, ObjectiveSummary, RevokeRuntimeTrustKeyRequest, - RuntimeTrustKeyRevealResponse, TICKET_ORCHESTRATION_PLANS_QUERY_PATH, - TICKET_RELATIONS_QUERY_PATH, WorkerLaunchOptionsResponse, WorkspaceRuntimeDetail, + RuntimeTrustKeyRevealResponse, WorkerLaunchOptionsResponse, WorkspaceRuntimeDetail, WorkspaceRuntimeResource, }; use ticket::{ @@ -22,21 +21,6 @@ use crate::{BackendApiClient, BackendWorkspaceClientError}; const DEFAULT_PRODUCT_LIST_LIMIT: usize = 1_000; -#[derive(Debug, Deserialize)] -struct BackendObjectiveListResponse { - workspace_id: String, - limit: usize, - items: Vec, - invalid_records: Vec, - record_authority: String, -} - -#[derive(Debug, Deserialize)] -struct BackendInvalidProjectRecord { - label: String, - reason: String, -} - /// Workspace-scoped Backend client for Ticket and Objective product state. /// /// Construction requires both the selected Backend URL and Workspace identity. @@ -85,26 +69,71 @@ impl BackendWorkspaceProductClient { &self.workspace_id } + fn generated(&self, operation: F) -> Result + where + F: FnOnce( + server_api::ServerApiClient, + ) -> Fut, + Fut: std::future::Future< + Output = Result< + T, + server_api::client_support::ClientError, + >, + >, + { + let client = crate::backend_workspace::server_api_client(&self.api)?; + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; + runtime + .block_on(operation(client)) + .map_err(|error| crate::backend_workspace::server_client_error(&self.api, error)) + } + pub fn list_tickets( &self, query: &TicketListQuery, ) -> Result, BackendWorkspaceClientError> { + let workspace_id = self.workspace_id.clone(); let state = ticket_list_state_query(query); - self.get_json(&format!("/tickets/search?state={state}")) + self.generated(move |client| async move { + client + .ticket_summary_search( + workspace_id, + server_api::TicketSummarySearchQuery { + state: Some(state), + limit: None, + }, + ) + .await + .map(|response| response.0) + }) } pub fn show_ticket(&self, id: &TicketIdOrSlug) -> Result { - self.get_json(&format!( - "/tickets/{}/record", - encode_path_segment(&ticket_reference(id)) - )) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(id); + self.generated(move |client| async move { + client + .ticket_record_get(workspace_id, id) + .await + .map(|response| response.0) + }) } pub fn create_ticket( &self, input: &NewTicket, ) -> Result { - self.send_json(Method::POST, "/tickets", Some(input)) + let workspace_id = self.workspace_id.clone(); + let input = input.clone(); + self.generated(move |client| async move { + client + .ticket_create_record(workspace_id, server_api::CreateTicketRecordRequest(input)) + .await + .map(|response| response.0) + }) } pub fn add_ticket_event( @@ -112,14 +141,18 @@ impl BackendWorkspaceProductClient { id: &TicketIdOrSlug, event: &NewTicketEvent, ) -> Result<(), BackendWorkspaceClientError> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/thread-events", - encode_path_segment(&ticket_reference(id)) - ), - Some(event), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(id); + let event = event.clone(); + self.generated(move |client| async move { + client + .ticket_thread_event_add( + workspace_id, + id, + server_api::TicketThreadEventRequest(event), + ) + .await + }) } pub fn set_ticket_workflow_state( @@ -127,14 +160,18 @@ impl BackendWorkspaceProductClient { id: &TicketIdOrSlug, change: &TicketStateChange, ) -> Result<(), BackendWorkspaceClientError> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/workflow-state", - encode_path_segment(&ticket_reference(id)) - ), - Some(change), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(id); + let change = change.clone(); + self.generated(move |client| async move { + client + .ticket_workflow_state_set( + workspace_id, + id, + server_api::TicketStateChangeRequest(change), + ) + .await + }) } pub fn close_ticket( @@ -142,14 +179,18 @@ impl BackendWorkspaceProductClient { id: &TicketIdOrSlug, resolution: &MarkdownText, ) -> Result<(), BackendWorkspaceClientError> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/workflow/close", - encode_path_segment(&ticket_reference(id)) - ), - Some(resolution), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(id); + let resolution = resolution.clone(); + self.generated(move |client| async move { + client + .ticket_close_record( + workspace_id, + id, + server_api::TicketCloseRecordRequest(resolution), + ) + .await + }) } pub fn add_ticket_relation( @@ -157,14 +198,19 @@ impl BackendWorkspaceProductClient { id: &TicketIdOrSlug, relation: &NewTicketRelation, ) -> Result { - self.send_json( - Method::POST, - &format!( - "/tickets/{}/relations", - encode_path_segment(&ticket_reference(id)) - ), - Some(relation), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(id); + let relation = relation.clone(); + self.generated(move |client| async move { + client + .ticket_relation_record( + workspace_id, + id, + server_api::CreateTicketRelationRequest(relation), + ) + .await + .map(|response| response.0) + }) } pub fn query_ticket_relations( @@ -172,28 +218,42 @@ impl BackendWorkspaceProductClient { ticket: Option<&TicketIdOrSlug>, kind: Option, ) -> Result, BackendWorkspaceClientError> { - #[derive(Serialize)] - struct Query<'a> { - ticket: Option<&'a TicketIdOrSlug>, - kind: Option, - } - self.send_json( - Method::POST, - TICKET_RELATIONS_QUERY_PATH, - Some(&Query { ticket, kind }), - ) + let workspace_id = self.workspace_id.clone(); + let ticket = ticket.cloned(); + self.generated(move |client| async move { + client + .ticket_relation_query( + workspace_id, + server_api::TicketRelationSearchRequest { ticket, kind }, + ) + .await + .map(|response| response.0) + }) } pub fn ticket_doctor(&self) -> Result { - self.get_json("/tickets/doctor") + let workspace_id = self.workspace_id.clone(); + self.generated(move |client| async move { + client + .ticket_doctor(workspace_id) + .await + .map(|response| response.0) + }) } pub fn list_objectives( &self, limit: usize, ) -> Result, BackendWorkspaceClientError> { - let response: BackendObjectiveListResponse = - self.get_json(&format!("/objectives?limit={limit}"))?; + let workspace_id = self.workspace_id.clone(); + let response = self.generated(move |client| async move { + client + .objective_list( + workspace_id, + server_api::ObjectiveListQuery { limit: Some(limit) }, + ) + .await + })?; Ok(ListResponse { workspace_id: response.workspace_id, limit: response.limit, @@ -212,14 +272,20 @@ impl BackendWorkspaceProductClient { } pub fn show_objective(&self, id: &str) -> Result { - self.get_json(&format!("/objectives/{}", encode_path_segment(id))) + let workspace_id = self.workspace_id.clone(); + let id = id.to_string(); + self.generated(move |client| async move { client.objective_get(workspace_id, id).await }) } pub fn create_objective( &self, input: &ObjectiveCreateRequest, ) -> Result { - self.send_json(Method::POST, "/objectives", Some(input)) + let workspace_id = self.workspace_id.clone(); + let input = input.clone(); + self.generated( + move |client| async move { client.objective_create(workspace_id, input).await }, + ) } pub fn edit_objective( @@ -227,10 +293,11 @@ impl BackendWorkspaceProductClient { id: &str, input: &ObjectiveEditRequest, ) -> Result { - self.send_json( - Method::PATCH, - &format!("/objectives/{}", encode_path_segment(id)), - Some(input), + let workspace_id = self.workspace_id.clone(); + let id = id.to_string(); + let input = input.clone(); + self.generated( + move |client| async move { client.objective_edit(workspace_id, id, input).await }, ) } @@ -239,11 +306,12 @@ impl BackendWorkspaceProductClient { id: &str, input: &ObjectiveStateRequest, ) -> Result { - self.send_json( - Method::POST, - &format!("/objectives/{}/state", encode_path_segment(id)), - Some(input), - ) + let workspace_id = self.workspace_id.clone(); + let id = id.to_string(); + let input = input.clone(); + self.generated(move |client| async move { + client.objective_state_set(workspace_id, id, input).await + }) } pub fn link_objective_ticket( @@ -251,11 +319,12 @@ impl BackendWorkspaceProductClient { id: &str, input: &ObjectiveLinkTicketRequest, ) -> Result { - self.send_json( - Method::POST, - &format!("/objectives/{}/ticket-links", encode_path_segment(id)), - Some(input), - ) + let workspace_id = self.workspace_id.clone(); + let id = id.to_string(); + let input = input.clone(); + self.generated(move |client| async move { + client.objective_ticket_link(workspace_id, id, input).await + }) } pub fn unlink_objective_ticket( @@ -263,15 +332,14 @@ impl BackendWorkspaceProductClient { id: &str, ticket_id: &str, ) -> Result { - self.send_json::<(), _>( - Method::DELETE, - &format!( - "/objectives/{}/ticket-links/{}", - encode_path_segment(id), - encode_path_segment(ticket_id) - ), - None, - ) + let workspace_id = self.workspace_id.clone(); + let id = id.to_string(); + let ticket_id = ticket_id.to_string(); + self.generated(move |client| async move { + client + .objective_ticket_unlink(workspace_id, id, ticket_id) + .await + }) } pub fn list_runtimes( @@ -387,17 +455,6 @@ impl BackendWorkspaceProductClient { response.json().map_err(BackendWorkspaceClientError::Http) } - fn send_unit( - &self, - method: Method, - path: &str, - body: Option<&B>, - ) -> Result<(), BackendWorkspaceClientError> { - let response = self.request(method, path, body)?.send()?; - self.api.check_status(response.status())?; - Ok(()) - } - fn request( &self, method: Method, @@ -419,15 +476,17 @@ impl BackendWorkspaceProductClient { impl TicketBackend for BackendWorkspaceProductClient { fn default_intake_ready_state_change_body(&self, from: &str) -> String { - #[derive(Serialize)] - struct Request<'a> { - from: &'a str, - } - self.send_json( - Method::POST, - "/tickets/default-intake-ready-body", - Some(&Request { from }), - ) + let workspace_id = self.workspace_id.clone(); + let from = from.to_string(); + self.generated(move |client| async move { + client + .ticket_default_intake_ready_body( + workspace_id, + server_api::DefaultIntakeReadyBodyRequest { from }, + ) + .await + .map(|response| response.0) + }) .unwrap_or_else(|error| error.to_string()) } @@ -444,22 +503,30 @@ impl TicketBackend for BackendWorkspaceProductClient { } fn edit_item(&self, id: TicketIdOrSlug, edit: TicketItemEdit) -> ticket::Result { - self.send_json( - Method::PATCH, - &format!( - "/tickets/{}/item", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&edit), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_record_item_edit( + workspace_id, + id, + server_api::EditTicketRecordItemRequest(edit), + ) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } fn dependency_check(&self, id: TicketIdOrSlug) -> ticket::Result { - self.get_json(&format!( - "/tickets/{}/dependency-check", - encode_path_segment(&ticket_reference(&id)) - )) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_dependency_check(workspace_id, id) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -473,14 +540,17 @@ impl TicketBackend for BackendWorkspaceProductClient { id: TicketIdOrSlug, change: TicketStateChange, ) -> ticket::Result<()> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/state-changes", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&change), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_state_change_add( + workspace_id, + id, + server_api::TicketStateChangeRequest(change), + ) + .await + }) .map_err(ticket_client_error) } @@ -489,14 +559,17 @@ impl TicketBackend for BackendWorkspaceProductClient { id: TicketIdOrSlug, summary: TicketIntakeSummary, ) -> ticket::Result<()> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/intake-summaries", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&summary), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_intake_summary_add( + workspace_id, + id, + server_api::TicketIntakeSummaryRequest(summary), + ) + .await + }) .map_err(ticket_client_error) } @@ -506,15 +579,19 @@ impl TicketBackend for BackendWorkspaceProductClient { field: &str, change: TicketStateChange, ) -> ticket::Result<()> { - self.send_unit( - Method::POST, - &format!( - "/tickets/{}/state-fields/{}", - encode_path_segment(&ticket_reference(&id)), - encode_path_segment(field) - ), - Some(&change), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + let field = field.to_string(); + self.generated(move |client| async move { + client + .ticket_state_field_set( + workspace_id, + id, + field, + server_api::TicketStateChangeRequest(change), + ) + .await + }) .map_err(ticket_client_error) } @@ -528,14 +605,19 @@ impl TicketBackend for BackendWorkspaceProductClient { } fn mark_ready(&self, id: TicketIdOrSlug, request: TicketMarkReady) -> ticket::Result { - self.send_json( - Method::POST, - &format!( - "/tickets/{}/workflow/mark-ready", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&request), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + let request = server_api::TicketMarkReadyRequest { + operation_key: request.operation_key, + reason: request.reason, + intake_summary: request.intake_summary, + }; + self.generated(move |client| async move { + client + .ticket_mark_ready_record(workspace_id, id, request) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -544,14 +626,14 @@ impl TicketBackend for BackendWorkspaceProductClient { id: TicketIdOrSlug, _queued_by: &str, ) -> ticket::Result { - self.send_json::<(), _>( - Method::POST, - &format!( - "/tickets/{}/workflow/queue", - encode_path_segment(&ticket_reference(&id)) - ), - None, - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_queue_record(workspace_id, id) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -575,22 +657,19 @@ impl TicketBackend for BackendWorkspaceProductClient { kind: TicketRelationKind, target: TicketIdOrSlug, ) -> ticket::Result { - #[derive(Serialize)] - struct Request { - kind: TicketRelationKind, - target: String, - } - self.send_json( - Method::DELETE, - &format!( - "/tickets/{}/relations", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&Request { - kind, - target: ticket_reference(&target), - }), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + let target = ticket_reference(&target); + self.generated(move |client| async move { + client + .ticket_relation_remove( + workspace_id, + id, + server_api::TicketRelationRemoveRequest { kind, target }, + ) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -604,10 +683,14 @@ impl TicketBackend for BackendWorkspaceProductClient { } fn relation_view(&self, id: TicketIdOrSlug) -> ticket::Result { - self.get_json(&format!( - "/tickets/{}/relation-view", - encode_path_segment(&ticket_reference(&id)) - )) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_relation_view(workspace_id, id) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -616,14 +699,18 @@ impl TicketBackend for BackendWorkspaceProductClient { id: TicketIdOrSlug, record: NewOrchestrationPlanRecord, ) -> ticket::Result { - self.send_json( - Method::POST, - &format!( - "/tickets/{}/orchestration-plans", - encode_path_segment(&ticket_reference(&id)) - ), - Some(&record), - ) + let workspace_id = self.workspace_id.clone(); + let id = ticket_reference(&id); + self.generated(move |client| async move { + client + .ticket_orchestration_plan_record( + workspace_id, + id, + server_api::CreateTicketOrchestrationPlanRequest(record), + ) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -632,16 +719,16 @@ impl TicketBackend for BackendWorkspaceProductClient { ticket: Option, kind: Option, ) -> ticket::Result> { - #[derive(Serialize)] - struct Query { - ticket: Option, - kind: Option, - } - self.send_json( - Method::POST, - TICKET_ORCHESTRATION_PLANS_QUERY_PATH, - Some(&Query { ticket, kind }), - ) + let workspace_id = self.workspace_id.clone(); + self.generated(move |client| async move { + client + .ticket_orchestration_plan_query( + workspace_id, + server_api::TicketOrchestrationPlanSearchRequest { ticket, kind }, + ) + .await + .map(|response| response.0) + }) .map_err(ticket_client_error) } @@ -649,7 +736,6 @@ impl TicketBackend for BackendWorkspaceProductClient { self.ticket_doctor().map_err(ticket_client_error) } } - fn ticket_client_error(error: BackendWorkspaceClientError) -> TicketError { TicketError::Sqlite(format!("Backend request failed: {error}")) } diff --git a/crates/merge-request/Cargo.toml b/crates/merge-request/Cargo.toml index 7c0b5664e..61fa80fe0 100644 --- a/crates/merge-request/Cargo.toml +++ b/crates/merge-request/Cargo.toml @@ -7,6 +7,7 @@ license.workspace = true [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } rusqlite.workspace = true +schemars = { workspace = true, features = ["chrono04"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true diff --git a/crates/merge-request/src/lib.rs b/crates/merge-request/src/lib.rs index c2777e4a2..154f3a773 100644 --- a/crates/merge-request/src/lib.rs +++ b/crates/merge-request/src/lib.rs @@ -18,7 +18,7 @@ const DOMAIN_TABLES: [&str; 5] = [ "merge_request_reviewer_child_sessions", ]; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum MergeRequestState { Open, @@ -43,13 +43,13 @@ impl MergeRequestState { } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ReviewDecision { Approve, RequestChanges, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum FindingSeverity { Blocker, @@ -57,7 +57,7 @@ pub enum FindingSeverity { Minor, Note, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReviewFinding { pub severity: FindingSeverity, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -68,7 +68,7 @@ pub struct ReviewFinding { pub line: Option, pub body: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct WorkerIdentity { pub runtime_id: String, pub worker_id: String, @@ -91,18 +91,20 @@ impl MergeRequestAuth { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReviewRequestedEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub subject_ref: String, pub requested_by: WorkerIdentity, pub reviewer: WorkerIdentity, pub created_at: DateTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReviewEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub request_event_id: String, pub subject_ref: String, @@ -112,9 +114,10 @@ pub struct ReviewEvent { pub reviewer: WorkerIdentity, pub created_at: DateTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReviewRevokedEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub review_event_id: String, pub subject_ref: String, @@ -122,39 +125,42 @@ pub struct ReviewRevokedEvent { pub revoked_by: WorkerIdentity, pub created_at: DateTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReviewCancelledEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub request_event_id: String, pub subject_ref: String, pub reason: String, pub created_at: DateTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct CommentEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub body: String, pub author: WorkerIdentity, pub created_at: DateTime, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum MergeStrategy { FastForward, Merge, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ConflictResolution { None, Clean, ConflictsResolved, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct MergeEvent { pub event_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub sequence: u64, pub operation_id: String, pub approval_event_id: String, @@ -166,7 +172,7 @@ pub struct MergeEvent { pub merged_by: WorkerIdentity, pub created_at: DateTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum MergeRequestThreadEvent { ReviewRequested(ReviewRequestedEvent), @@ -203,7 +209,7 @@ impl MergeRequestThreadEvent { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct MergeRequest { pub workspace_id: String, pub merge_request_id: String, @@ -313,7 +319,7 @@ pub struct ReadinessCheck { pub current_subject_ref: Option, pub auth: MergeRequestAuth, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct ReadinessReport { pub ready: bool, pub blockers: Vec, diff --git a/crates/server-api/Cargo.toml b/crates/server-api/Cargo.toml index d3b9def43..a74b4c020 100644 --- a/crates/server-api/Cargo.toml +++ b/crates/server-api/Cargo.toml @@ -12,12 +12,14 @@ typescript = ["dep:ts-rs", "protocol/typescript"] [dependencies] api-macros = { workspace = true, features = ["reqwest", "axum", "openapi"] } memory.workspace = true +merge-request.workspace = true protocol.workspace = true runtime-api.workspace = true schemars.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true sha2.workspace = true +ticket.workspace = true ts-rs = { version = "12.0.1", optional = true } webauthn-rs-proto = "0.5.5" workdir.workspace = true diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index 568bab327..ba6632d7d 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -152,6 +152,75 @@ impl_openapi_schema!( SkillCatalogResponse, SkillDetailResponse, SkillActivationResponse, + BrowserAppendTicketEventRequest, + BrowserCloseTicketRequest, + BrowserEditTicketRequest, + BrowserQueueTicketRequest, + BrowserTransitionTicketStateRequest, + CancelTicketImplementationRequest, + ClearTicketRoleAssignmentQuery, + CompleteMergeRequestRequest, + CreateTicketOrchestrationPlanRequest, + CreateTicketRecordRequest, + CreateTicketRelationRequest, + DefaultIntakeReadyBodyRequest, + EditTicketRecordItemRequest, + MergeEvent, + MergeRequestDetailResponse, + MergeRequestListQuery, + MergeRequestListResponse, + MergeRequestReadinessResponse, + MergeRequestThreadQuery, + MergeRequestThreadResponse, + ObjectiveCreateRequest, + ObjectiveDetail, + ObjectiveEditRequest, + ObjectiveLinkTicketRequest, + ObjectiveListQuery, + ObjectiveListResponse, + ObjectiveQueryRequest, + ObjectiveQueryResponse, + ObjectiveShowRequest, + ObjectiveStateRequest, + OpenMergeRequestRequest, + PublicMergeRequest, + RegisterMergeRequestReviewCapabilityRequest, + RegisterReviewerChildSessionRequest, + RepairMergeRequestSelectorRequest, + ReviewEvent, + ReviewRevokedEvent, + RevokeMergeRequestReviewRequest, + SetTicketRoleAssignmentRequest, + SubmitMergeRequestReviewRequest, + TextResponse, + TicketCloseRecordRequest, + TicketDependencyCheckResponse, + TicketDetail, + TicketDoctorResponse, + TicketIntakeSummaryRequest, + TicketListHttpQuery, + TicketListResponse, + TicketMarkReadyRequest, + TicketOrchestrationPlanRecord, + TicketOrchestrationPlanRecordList, + TicketOrchestrationPlanSearchRequest, + TicketQueryRequest, + TicketQueryResponse, + TicketQueueResponse, + TicketRecord, + TicketRecordRef, + TicketRecordSummaryList, + TicketRelationRecord, + TicketRelationRecordList, + TicketRelationRecordView, + TicketRelationRemoveRequest, + TicketRelationSearchRequest, + TicketRoleAssignmentMutationResponse, + TicketRoleAssignmentsResponse, + TicketShowRequest, + TicketStateChangeRequest, + TicketSummarySearchQuery, + TicketThreadEventRequest, ); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -910,6 +979,478 @@ pub trait ServerApi { #[path] workspace_id: String, #[body] request: CreateWorkspaceRepositoryRequest, ) -> Result; + + #[get("/api/tickets", status = 200, error_status = 400, additional_error_statuses = [500])] + async fn ticket_list_alias( + &self, + #[query] query: TicketListHttpQuery, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_list( + &self, + #[path] workspace_id: String, + #[query] query: TicketListHttpQuery, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_create_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: CreateTicketRecordRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/query", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_query( + &self, + #[path] workspace_id: String, + #[body] request: TicketQueryRequest, + ) -> Result; + + #[get("/api/tickets/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500])] + async fn ticket_get_alias( + &self, + #[path] id: String, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/default-intake-ready-body", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_default_intake_ready_body( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: DefaultIntakeReadyBodyRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_summary_search( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[query] query: TicketSummarySearchQuery, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/doctor", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_doctor( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/relations/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_relation_query( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: TicketRelationSearchRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/orchestration-plans/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_orchestration_plan_query( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: TicketOrchestrationPlanSearchRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}/record", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500])] + async fn ticket_record_get( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[patch("/api/w/{workspace_id}/tickets/{id}/item", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_record_item_edit( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: EditTicketRecordItemRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}/dependency-check", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_dependency_check( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/thread-events", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_thread_event_add( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketThreadEventRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/state-changes", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_state_change_add( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketStateChangeRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/intake-summaries", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_intake_summary_add( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketIntakeSummaryRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/state-fields/{field}", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_state_field_set( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[path] field: String, + #[body] request: TicketStateChangeRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/workflow-state", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_workflow_state_set( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketStateChangeRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/workflow/mark-ready", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_mark_ready_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketMarkReadyRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/workflow/queue", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_queue_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[get("/api/w/{workspace_id}/merge-requests", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)] + async fn merge_request_list( + &self, + #[path] workspace_id: String, + #[query] query: MergeRequestListQuery, + ) -> Result; + + #[get("/api/w/{workspace_id}/merge-requests/{merge_request_id}", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500], bearer_auth = true, browser_auth = true)] + async fn merge_request_show( + &self, + #[path] workspace_id: String, + #[path] merge_request_id: String, + #[query] query: MergeRequestThreadQuery, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_open( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: OpenMergeRequestRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}/merge-request/readiness", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_readiness( + &self, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}/merge-request/thread", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn merge_request_thread( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[query] query: MergeRequestThreadQuery, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request/repair-source", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn merge_request_selector_repair( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: RepairMergeRequestSelectorRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/internal/reviewer-child-sessions", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_reviewer_child_register( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: RegisterReviewerChildSessionRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request/review-capabilities", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_review_capability_register( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: RegisterMergeRequestReviewCapabilityRequest, + ) -> Result<(), RepositoryApiError>; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_review_submit( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: SubmitMergeRequestReviewRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews/revoke", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_review_revoke( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: RevokeMergeRequestReviewRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/merge-request/complete", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn merge_request_complete( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: CompleteMergeRequestRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/workflow/close", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_close_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketCloseRecordRequest, + ) -> Result<(), RepositoryApiError>; + + #[get("/api/w/{workspace_id}/tickets/{id}/relation-view", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_relation_view( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_relation_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: CreateTicketRelationRequest, + ) -> Result; + + #[delete("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_relation_remove( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketRelationRemoveRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/orchestration-plans", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])] + async fn ticket_orchestration_plan_record( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: CreateTicketOrchestrationPlanRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_get( + &self, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[patch("/api/w/{workspace_id}/tickets/{id}", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_edit( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: BrowserEditTicketRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn ticket_show( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketShowRequest, + ) -> Result; + + #[get("/api/w/{workspace_id}/tickets/{id}/assignments", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_assignment_list( + &self, + #[path] workspace_id: String, + #[path] id: String, + ) -> Result; + + #[put("/api/w/{workspace_id}/tickets/{id}/assignments/{role}", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_assignment_set( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[path] role: String, + #[body] request: SetTicketRoleAssignmentRequest, + ) -> Result; + + #[delete("/api/w/{workspace_id}/tickets/{id}/assignments/{role}", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_assignment_clear( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[path] role: String, + #[query] query: ClearTicketRoleAssignmentQuery, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/implementation-cancellations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_implementation_cancel( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: CancelTicketImplementationRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/state", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_state_transition( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: BrowserTransitionTicketStateRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/ready", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_ready( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: TicketMarkReadyRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/events", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_event_append( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: BrowserAppendTicketEventRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/queue", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_queue( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: BrowserQueueTicketRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/tickets/{id}/close", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn ticket_close( + &self, + #[path] workspace_id: String, + #[path] id: String, + #[body] request: BrowserCloseTicketRequest, + ) -> Result; + + #[get("/api/objectives", status = 200, error_status = 400, additional_error_statuses = [500])] + async fn objective_list_alias( + &self, + #[query] query: ObjectiveListQuery, + ) -> Result; + + #[get("/api/w/{workspace_id}/objectives", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)] + async fn objective_list( + &self, + #[path] workspace_id: String, + #[query] query: ObjectiveListQuery, + ) -> Result; + + #[post("/api/w/{workspace_id}/objectives", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn objective_create( + &self, + #[path] workspace_id: String, + #[body] request: ObjectiveCreateRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/objectives/query", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn objective_query( + &self, + #[path] workspace_id: String, + #[body] request: ObjectiveQueryRequest, + ) -> Result; + + #[get("/api/objectives/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500])] + async fn objective_get_alias( + &self, + #[path] id: String, + ) -> Result; + + #[get("/api/w/{workspace_id}/objectives/{objective_id}", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500], bearer_auth = true, browser_auth = true)] + async fn objective_get( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + ) -> Result; + + #[patch("/api/w/{workspace_id}/objectives/{objective_id}", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn objective_edit( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + #[body] request: ObjectiveEditRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/objectives/{objective_id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])] + async fn objective_show( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + #[body] request: ObjectiveShowRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/objectives/{objective_id}/state", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn objective_state_set( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + #[body] request: ObjectiveStateRequest, + ) -> Result; + + #[post("/api/w/{workspace_id}/objectives/{objective_id}/ticket-links", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn objective_ticket_link( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + #[body] request: ObjectiveLinkTicketRequest, + ) -> Result; + + #[delete("/api/w/{workspace_id}/objectives/{objective_id}/ticket-links/{ticket_id}", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)] + async fn objective_ticket_unlink( + &self, + #[path] workspace_id: String, + #[path] objective_id: String, + #[path] ticket_id: String, + ) -> Result; } /// Digest of the fully rendered canonical contract with its digest slot normalized. @@ -947,10 +1488,27 @@ pub fn canonical_openapi_document() /// /// Generated clients and OpenAPI omit extension parameters; operation implementations use this /// value instead of re-parsing transport headers inside domain handlers. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct ServerRequestContext { pub actor: Option, pub origin: Option, + /// Transport headers retained for Server-side authentication adapters. + /// + /// Generated clients and OpenAPI do not expose this extension. Values stay byte-exact so the + /// Workspace Server can reuse the existing Runtime source-proof and browser-session authority + /// while generated Axum routes replace only the handwritten route registration. + pub transport_headers: Vec<(String, Vec)>, +} + +impl std::fmt::Debug for ServerRequestContext { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("ServerRequestContext") + .field("actor", &self.actor) + .field("origin", &self.origin) + .field("transport_header_count", &self.transport_headers.len()) + .finish() + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -2933,9 +3491,10 @@ pub struct WorkingDirectoryCreateResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] pub struct ListResponse { pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, pub items: Vec, pub source: String, @@ -2943,19 +3502,28 @@ pub struct ListResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct QueryPage { + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub returned: usize, pub has_more: bool, pub next_cursor: Option, pub sort: String, + #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub source_limit: Option, pub source_truncated: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveEventDetail { pub event_ref: String, pub kind: String, @@ -2963,7 +3531,9 @@ pub struct ObjectiveEventDetail { pub created_at: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveLinkedTicketSummary { pub id: String, pub resource_key: String, @@ -2971,15 +3541,21 @@ pub struct ObjectiveLinkedTicketSummary { pub state: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveResourceSummary { pub path: String, pub media_type: Option, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub bytes: usize, pub updated_at: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveSummary { pub id: String, pub resource_key: String, @@ -2992,7 +3568,9 @@ pub struct ObjectiveSummary { pub record_source: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveDetail { pub id: String, pub resource_key: String, @@ -3011,7 +3589,9 @@ pub struct ObjectiveDetail { pub record_source: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveCreateRequest { pub title: String, #[serde(default)] @@ -3026,7 +3606,9 @@ fn default_objective_state() -> String { "active".to_string() } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveEditRequest { pub title: Option, pub old_string: Option, @@ -3035,17 +3617,1005 @@ pub struct ObjectiveEditRequest { pub replace_all: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveStateRequest { pub state: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] pub struct ObjectiveLinkTicketRequest { pub ticket_id: String, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +/// One malformed project record reported by a bounded list projection. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct InvalidProjectRecord { + pub label: String, + pub reason: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketListItemSummary { + pub id: String, + pub resource_key: String, + pub title: String, + pub state: String, + pub priority: String, + pub updated_at: Option, + pub queued_by: Option, + pub queued_at: Option, + pub workspace_action_priority: String, + pub record_source: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketListHttpQuery { + pub states: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, + pub cursor: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketListResponse { + pub workspace_id: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 1000))] + pub limit: usize, + pub items: Vec, + pub page: QueryPage, + pub invalid_records: Vec, + pub record_authority: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketEventDetail { + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub sequence: usize, + pub event_ref: String, + pub kind: String, + pub author: Option, + pub at: Option, + pub status: Option, + pub from: Option, + pub to: Option, + pub reason: Option, + pub state_field: Option, + pub heading: Option, + pub body: Option, + pub attributes: BTreeMap, + pub references: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetailRelation { + pub ticket_id: String, + pub kind: String, + pub target: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub target_resource_key: Option, + pub note: Option, + pub author: String, + pub at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetailDerivedRelation { + pub source_ticket: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub source_resource_key: Option, + pub inverse_kind: String, + pub forward_kind: String, + pub note: Option, + pub author: String, + pub at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetailRelationBlocker { + pub blocking_ticket: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub blocking_resource_key: Option, + pub reason_kind: String, + pub relation_kind: String, + pub note: Option, + pub blocking_state: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetailRelationNotice { + pub related_ticket: String, + pub kind: String, + pub message: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetailRelationView { + pub outgoing: Vec, + pub incoming: Vec, + pub blockers: Vec, + pub notices: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveLinkSummary { + pub id: String, + pub resource_key: String, + pub title: String, + pub state: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketEvidenceEvent { + pub event_ref: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub sequence: usize, + pub kind: String, + pub at: Option, + pub author: Option, + pub excerpt: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketAssignmentSummary { + pub assignment_id: String, + pub runtime_id: String, + pub worker_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub worker_resource_key: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] +#[cfg_attr(feature = "typescript", ts(tag = "kind", rename_all = "snake_case"))] +pub enum TicketAssignmentPrincipal { + User { + account_id: String, + }, + Worker { + runtime_id: String, + worker_id: String, + }, + WorkspaceAgent { + agent_key: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum TicketAssignmentRole { + Orchestrator, + Coder, + Owner, + Contributor, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketRoleAssignmentRecord { + pub workspace_id: String, + pub ticket_id: String, + pub assignment_id: String, + pub role: TicketAssignmentRole, + pub principal: TicketAssignmentPrincipal, + pub assigned_by: String, + pub assigned_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketRoleAssignmentSummary { + pub assignment_id: String, + pub role: String, + pub principal: TicketAssignmentPrincipal, + pub assigned_by: String, + pub assigned_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketActionEligibility { + pub can_assign_orchestrator: bool, + pub can_unassign_orchestrator: bool, + pub can_queue: bool, + pub can_start_manual_coder: bool, + pub queue_tickets: Vec, + pub blockers: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketMergeRequestSummary { + pub merge_request_id: String, + pub repository_key: String, + pub state: String, + pub review_status: String, + pub selector_from: Option, + pub selector_to: String, + pub updated_at: String, + pub current_subject_ref: Option, + pub review_subject_ref: Option, + pub review_requested_at: Option, + pub review_submitted_at: Option, + pub review_excerpt: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestRefDiagnostic { + pub code: String, + pub message: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestListItem { + pub summary: TicketMergeRequestSummary, + pub ticket_ids: Vec, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub thread_event_count: usize, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub ref_diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestListResponse { + pub items: Vec, + pub next_cursor: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketEvidenceSummary { + pub has_merge_request: bool, + pub has_current_subject_ref: bool, + pub has_review_request: bool, + pub has_commit: bool, + pub review_status: Option, + pub approved_current_subject: bool, + pub review_after_rescope: bool, + pub unresolved_request_changes: bool, + pub complete_for_integration: bool, + pub missing: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketQueryRequest { + pub query: Option, + #[serde(default)] + pub states: Vec, + #[serde(default)] + pub event_kinds: Vec, + #[serde(default)] + pub evidence: Vec, + pub review_status: Option, + #[serde(default)] + pub attention: Vec, + pub related_ticket_id: Option, + pub relation_kind: Option, + pub linked_objective_id: Option, + pub updated_after: Option, + pub updated_before: Option, + pub sort: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, + pub cursor: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketQueryItem { + pub id: String, + pub resource_key: String, + pub title: String, + pub state: String, + pub readiness: Option, + pub priority: String, + pub created_at: Option, + pub updated_at: Option, + pub item_revision: String, + pub workspace_action_priority: String, + pub matched_fields: Vec, + pub snippet: Option, + pub matching_event: Option, + pub linked_objective_ids: Vec, + pub linked_objective_keys: Vec, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub relation_count: usize, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub blocker_count: usize, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub unresolved_blocker_count: usize, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub unresolved_review_count: usize, + pub evidence: TicketEvidenceSummary, + pub merge_request: Option, + pub current_coder: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketQueryResponse { + pub items: Vec, + pub page: QueryPage, + pub record_authority: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketShowRequest { + #[schemars(range(min = 0, max = 1000))] + pub event_limit: Option, + pub event_cursor: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketDetail { + pub id: String, + pub resource_key: String, + pub title: String, + pub state: String, + pub readiness: Option, + pub priority: String, + pub created_at: Option, + pub updated_at: Option, + pub item_revision: String, + pub queued_by: Option, + pub queued_at: Option, + pub repository_key: Option, + pub ref_selector: Option, + pub risk_flags: Vec, + pub body: String, + pub body_truncated: bool, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub event_count: usize, + pub events: Vec, + pub event_page: QueryPage, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub artifact_count: usize, + pub artifacts: Vec, + pub relations: TicketDetailRelationView, + pub linked_objectives: Vec, + pub implementation_reports: Vec, + pub assignments: Vec, + pub current_coder: Option, + pub assignment_diagnostics: Vec, + pub action_eligibility: TicketActionEligibility, + pub merge_request: Option, + pub evidence: TicketEvidenceSummary, + pub resolution: Option, + pub record_source: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct DefaultIntakeReadyBodyRequest { + pub from: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct TextResponse(pub String); + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketSummarySearchQuery { + pub state: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, +} + +macro_rules! transparent_ticket_dto { + ($name:ident, $inner:ty) => { + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] + #[serde(transparent)] + pub struct $name(pub $inner); + }; +} + +transparent_ticket_dto!(TicketRecord, ticket::Ticket); +transparent_ticket_dto!(TicketRecordRef, ticket::TicketRef); +transparent_ticket_dto!(TicketRecordSummaryList, Vec); +transparent_ticket_dto!(TicketDependencyCheckResponse, ticket::TicketDependencyCheck); +transparent_ticket_dto!(TicketQueueResponse, ticket::TicketQueueOutcome); +transparent_ticket_dto!(TicketDoctorResponse, ticket::TicketDoctorReport); +transparent_ticket_dto!(TicketRelationRecord, ticket::TicketRelation); +transparent_ticket_dto!(TicketRelationRecordList, Vec); +transparent_ticket_dto!(TicketRelationRecordView, ticket::TicketRelationView); +transparent_ticket_dto!( + TicketOrchestrationPlanRecord, + ticket::OrchestrationPlanRecord +); +transparent_ticket_dto!( + TicketOrchestrationPlanRecordList, + Vec +); +transparent_ticket_dto!(CreateTicketRecordRequest, ticket::NewTicket); +transparent_ticket_dto!(EditTicketRecordItemRequest, ticket::TicketItemEdit); +transparent_ticket_dto!(TicketThreadEventRequest, ticket::NewTicketEvent); +transparent_ticket_dto!(TicketStateChangeRequest, ticket::TicketStateChange); +transparent_ticket_dto!(TicketIntakeSummaryRequest, ticket::TicketIntakeSummary); +transparent_ticket_dto!(TicketCloseRecordRequest, ticket::MarkdownText); +transparent_ticket_dto!(CreateTicketRelationRequest, ticket::NewTicketRelation); +transparent_ticket_dto!( + CreateTicketOrchestrationPlanRequest, + ticket::NewOrchestrationPlanRecord +); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketMarkReadyRequest { + pub operation_key: String, + #[serde(default)] + pub reason: Option, + #[serde(default)] + pub intake_summary: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketRelationRemoveRequest { + pub kind: ticket::TicketRelationKind, + pub target: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketRelationSearchRequest { + pub ticket: Option, + pub kind: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct TicketOrchestrationPlanSearchRequest { + pub ticket: Option, + pub kind: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct SetTicketRoleAssignmentRequest { + pub operation_id: String, + pub principal: TicketAssignmentPrincipal, + pub expected_assignment_id: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ClearTicketRoleAssignmentQuery { + pub operation_id: Option, + pub assignment_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct CancelTicketImplementationRequest { + pub operation_id: String, + pub assignment_id: String, + pub reason: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketRoleAssignmentsResponse { + pub workspace_id: String, + pub ticket_id: String, + pub assignments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct TicketRoleAssignmentMutationResponse { + pub workspace_id: String, + pub ticket_id: String, + pub assignment: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)] +#[cfg_attr(feature = "typescript", ts(tag = "action", rename_all = "snake_case"))] +pub enum BrowserTicketTargetEdit { + Set { + repository_key: String, + ref_selector: Option, + }, + Clear, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum BrowserTicketWorkflowState { + Planning, + Ready, + Queued, + Inprogress, + Done, + Closed, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct BrowserEditTicketRequest { + pub title: Option, + pub body: Option, + pub old_string: Option, + pub new_string: Option, + #[serde(default)] + pub replace_all: bool, + pub target: Option, + pub author: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct BrowserTransitionTicketStateRequest { + pub state: BrowserTicketWorkflowState, + pub reason: Option, + pub body: Option, + pub author: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum BrowserTicketThreadRole { + Comment, + Plan, + Decision, + ImplementationReport, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct BrowserAppendTicketEventRequest { + pub role: BrowserTicketThreadRole, + pub body: String, + pub author: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct BrowserQueueTicketRequest {} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct BrowserCloseTicketRequest { + pub resolution: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct ObjectiveListQuery { + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveListResponse { + pub workspace_id: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 1000))] + pub limit: usize, + pub items: Vec, + pub invalid_records: Vec, + pub record_authority: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveQueryRequest { + pub query: Option, + #[serde(default)] + pub states: Vec, + pub linked_ticket_id: Option, + pub updated_after: Option, + pub updated_before: Option, + pub sort: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, + pub cursor: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveQueryItem { + pub id: String, + pub resource_key: String, + pub title: String, + pub state: String, + pub created_at: Option, + pub updated_at: Option, + pub matched_fields: Vec, + pub snippet: Option, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub linked_ticket_count: usize, + pub linked_tickets: Vec, + pub linked_ticket_keys: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveQueryResponse { + pub items: Vec, + pub page: QueryPage, + pub record_authority: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ObjectiveShowRequest { + #[schemars(range(min = 0, max = 1000))] + pub event_limit: Option, + pub event_cursor: Option, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum MergeRequestState { + Open, + Merged, + Closed, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum ReviewDecision { + Approve, + RequestChanges, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum FindingSeverity { + Blocker, + Major, + Minor, + Note, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct ReviewFinding { + pub severity: FindingSeverity, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub line: Option, + pub body: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestWorkerIdentity { + pub runtime_id: String, + pub worker_id: String, +} + +macro_rules! merge_request_event { + ($name:ident { $($field:ident : $ty:ty),* $(,)? }) => { + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] + #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] + #[serde(deny_unknown_fields)] + pub struct $name { + pub event_id: String, + #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub sequence: u64, + $(pub $field: $ty,)* + pub created_at: String, + } + }; +} + +merge_request_event!(ReviewRequestedEvent { + subject_ref: String, + requested_by: MergeRequestWorkerIdentity, + reviewer: MergeRequestWorkerIdentity, +}); +merge_request_event!(ReviewEvent { + request_event_id: String, + subject_ref: String, + decision: ReviewDecision, + body: String, + findings: Vec, + reviewer: MergeRequestWorkerIdentity, +}); +merge_request_event!(ReviewRevokedEvent { + review_event_id: String, + subject_ref: String, + reason: String, + revoked_by: MergeRequestWorkerIdentity, +}); +merge_request_event!(ReviewCancelledEvent { + request_event_id: String, + subject_ref: String, + reason: String, +}); +merge_request_event!(MergeRequestCommentEvent { + body: String, + author: MergeRequestWorkerIdentity, +}); + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum MergeStrategy { + FastForward, + Merge, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum ConflictResolution { + None, + Clean, + ConflictsResolved, +} + +merge_request_event!(MergeEvent { + operation_id: String, + approval_event_id: String, + approved_source_ref: String, + target_ref_before: String, + target_ref_after: String, + strategy: MergeStrategy, + resolution: ConflictResolution, + merged_by: MergeRequestWorkerIdentity, +}); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum MergeRequestThreadEvent { + ReviewRequested(ReviewRequestedEvent), + Review(ReviewEvent), + ReviewRevoked(ReviewRevokedEvent), + ReviewCancelled(ReviewCancelledEvent), + Comment(MergeRequestCommentEvent), + Merge(MergeEvent), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct PublicMergeRequest { + pub workspace_id: String, + pub merge_request_id: String, + pub repository_key: String, + pub state: MergeRequestState, + pub selector_from: Option, + pub selector_to: String, + pub ticket_ids: Vec, + pub created_at: String, + pub updated_at: String, + pub thread: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MergeRequestListQuery { + pub state: Option, + pub repository_key: Option, + pub ticket_ref: Option, + pub selector_from: Option, + pub selector_to: Option, + pub cursor: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct MergeRequestThreadQuery { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub after: Option, + #[schemars(range(min = 0, max = 1000))] + pub limit: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestRefResponse { + pub status: String, + #[serde(rename = "ref")] + pub revision_ref: Option, + pub observed_at: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub diagnostic: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestLinkedTicketResponse { + pub ticket_id: String, + pub key: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestDetailResponse { + #[serde(flatten)] + pub merge_request: PublicMergeRequest, + pub source: MergeRequestRefResponse, + pub target: MergeRequestRefResponse, + pub linked_tickets: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] +#[serde(deny_unknown_fields)] +pub struct MergeRequestReadinessResponse { + pub ready: bool, + pub blockers: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub subject_ref: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "typescript", ts(optional = nullable))] + pub review: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct MergeRequestThreadResponse(pub Vec); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct OpenMergeRequestRequest { + pub repository_key: String, + pub selector_from: String, + pub selector_to: String, + #[serde(default)] + pub summary: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RepairMergeRequestSelectorRequest { + pub selector_from: String, + pub reason: String, + pub explicit_confirmation: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RevokeMergeRequestReviewRequest { + pub review_event_id: String, + pub reason: String, + pub explicit_confirmation: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RegisterReviewerChildSessionRequest { + pub child_session_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RegisterMergeRequestReviewCapabilityRequest { + pub child_session_id: String, + pub capability_token: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct SubmitMergeRequestReviewRequest { + pub capability_token: String, + pub decision: ReviewDecision, + #[serde(default)] + pub body: String, + #[serde(default)] + pub findings: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CompleteMergeRequestRequest { + pub operation_id: String, + pub approval_event_id: String, + pub target_ref_before: String, + pub target_ref_after: String, + pub strategy: MergeStrategy, + pub resolution: ConflictResolution, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeSourceKind { @@ -5123,6 +6693,61 @@ mod tests { } } + #[test] + fn ticket_objective_and_merge_request_operations_are_in_server_api_metadata() { + let operations = ServerApiMetadata::OPERATIONS; + assert_eq!( + operations + .iter() + .filter(|operation| operation.operation_id.starts_with("ticket_")) + .count(), + 37 + ); + assert_eq!( + operations + .iter() + .filter(|operation| operation.operation_id.starts_with("objective_")) + .count(), + 11 + ); + assert_eq!( + operations + .iter() + .filter(|operation| operation.operation_id.starts_with("merge_request_")) + .count(), + 11 + ); + for (operation_id, method, path) in [ + ( + "ticket_relation_query", + HttpMethod::Post, + "/api/w/{workspace_id}/tickets/relations/search", + ), + ( + "ticket_orchestration_plan_query", + HttpMethod::Post, + "/api/w/{workspace_id}/tickets/orchestration-plans/search", + ), + ( + "merge_request_complete", + HttpMethod::Post, + "/api/w/{workspace_id}/tickets/{id}/merge-request/complete", + ), + ( + "objective_ticket_unlink", + HttpMethod::Delete, + "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links/{ticket_id}", + ), + ] { + let operation = operations + .iter() + .find(|operation| operation.operation_id == operation_id) + .unwrap_or_else(|| panic!("missing ServerApi operation {operation_id}")); + assert_eq!(operation.method, method, "{operation_id}"); + assert_eq!(operation.path, path, "{operation_id}"); + } + } + #[test] fn workspace_configuration_domain_operations_are_in_server_api_metadata() { let operations = ServerApiMetadata::OPERATIONS; diff --git a/crates/ticket/src/lib.rs b/crates/ticket/src/lib.rs index 56531a027..3dba32a05 100644 --- a/crates/ticket/src/lib.rs +++ b/crates/ticket/src/lib.rs @@ -133,7 +133,19 @@ fn sqlite_err(error: impl std::fmt::Display) -> TicketError { TicketError::Sqlite(error.to_string()) } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + schemars::JsonSchema, +)] pub enum TicketStatus { Open, Closed, @@ -162,7 +174,9 @@ impl fmt::Display for TicketStatus { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema, +)] pub enum ExtensibleTicketStatus { Open, Closed, @@ -206,7 +220,19 @@ impl From for ExtensibleTicketStatus { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + schemars::JsonSchema, +)] #[serde(rename_all = "snake_case")] pub enum TicketWorkflowState { Planning, @@ -273,7 +299,7 @@ impl fmt::Display for TicketWorkflowState { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct MarkdownText(pub String); impl MarkdownText { @@ -298,7 +324,7 @@ impl From for MarkdownText { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub enum TicketIdOrSlug { Id(String), Slug(String), @@ -325,7 +351,7 @@ impl From for TicketIdOrSlug { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TicketEventKind { Create, @@ -390,13 +416,13 @@ impl From<&str> for TicketEventKind { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketReference { pub kind: String, pub target: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct NewTicketEvent { pub kind: TicketEventKind, pub author: Option, @@ -415,7 +441,7 @@ impl NewTicketEvent { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketStateChange { pub from: String, pub to: String, @@ -443,7 +469,7 @@ impl TicketStateChange { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketIntakeSummary { pub author: Option, pub body: MarkdownText, @@ -460,7 +486,7 @@ impl TicketIntakeSummary { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct NewTicket { pub title: String, pub slug: Option, @@ -533,7 +559,7 @@ pub trait TicketTargetAuthority: Send + Sync { ) -> Result; } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketMarkReady { pub operation_key: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -685,7 +711,7 @@ fn validate_generic_state_change( Ok(()) } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketItemEdit { pub title: Option, /// Whole-body replacement for legacy authoring surfaces. Prefer `body_replacement` @@ -697,7 +723,7 @@ pub struct TicketItemEdit { pub author: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketBodyReplacement { pub old_string: String, pub new_string: String, @@ -787,7 +813,7 @@ impl TicketBodyReplacement { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketDependencyCheck { pub ticket: TicketSummary, pub blockers: Vec, @@ -796,7 +822,7 @@ pub struct TicketDependencyCheck { pub recommended_action: TicketWorkspaceNextAction, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketQueueOutcome { pub requested_ticket: String, pub queued_tickets: Vec, @@ -923,7 +949,7 @@ impl TicketListQuery { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketRef { pub id: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -932,7 +958,19 @@ pub struct TicketRef { pub status: TicketStatus, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + schemars::JsonSchema, +)] #[serde(rename_all = "snake_case")] pub enum TicketRelationKind { DependsOn, @@ -971,7 +1009,7 @@ impl fmt::Display for TicketRelationKind { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct NewTicketRelation { pub kind: TicketRelationKind, pub target: String, @@ -979,7 +1017,7 @@ pub struct NewTicketRelation { pub author: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(deny_unknown_fields)] pub struct TicketRelation { pub ticket_id: String, @@ -991,7 +1029,7 @@ pub struct TicketRelation { pub at: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct DerivedTicketRelation { pub source_ticket: String, pub inverse_kind: String, @@ -1001,7 +1039,7 @@ pub struct DerivedTicketRelation { pub at: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketRelationBlocker { pub blocking_ticket: String, pub reason_kind: String, @@ -1010,14 +1048,14 @@ pub struct TicketRelationBlocker { pub blocking_state: TicketWorkflowState, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketRelationNotice { pub related_ticket: String, pub kind: TicketRelationKind, pub message: String, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketRelationView { pub outgoing: Vec, pub incoming: Vec, @@ -1033,7 +1071,7 @@ pub enum TicketWorkspaceActionPriority { Background, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TicketWorkspaceNextAction { Clarify, @@ -1057,7 +1095,7 @@ pub struct TicketWorkspaceStateOverlay { pub workflow_state: TicketWorkflowState, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketQueueGuard { pub can_queue_for_orchestrator: bool, pub reason: Option, @@ -1473,7 +1511,19 @@ fn format_workspace_relation_blockers(blockers: &[&TicketRelationBlocker]) -> St formatted } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + schemars::JsonSchema, +)] #[serde(rename_all = "snake_case")] pub enum OrchestrationPlanKind { Before, @@ -1533,7 +1583,7 @@ impl fmt::Display for OrchestrationPlanKind { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(deny_unknown_fields)] pub struct AcceptedOrchestrationPlan { pub summary: String, @@ -1545,7 +1595,7 @@ pub struct AcceptedOrchestrationPlan { pub role_plan: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct NewOrchestrationPlanRecord { pub kind: OrchestrationPlanKind, pub related_ticket: Option, @@ -1554,7 +1604,7 @@ pub struct NewOrchestrationPlanRecord { pub author: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(deny_unknown_fields)] pub struct OrchestrationPlanRecord { pub id: String, @@ -1570,7 +1620,7 @@ pub struct OrchestrationPlanRecord { pub at: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketMeta { pub id: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1596,7 +1646,7 @@ pub struct TicketMeta { pub raw: BTreeMap, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketSummary { pub id: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1651,13 +1701,13 @@ pub struct SqliteTicketListPage { pub next: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketDocument { pub body: MarkdownText, pub raw_frontmatter: BTreeMap, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketEvent { pub kind: TicketEventKind, pub author: Option, @@ -1673,13 +1723,13 @@ pub struct TicketEvent { pub attributes: BTreeMap, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketArtifactRef { /// Path relative to the ticket's `artifacts/` directory. pub relative_path: PathBuf, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct Ticket { pub meta: TicketMeta, pub document: TicketDocument, @@ -1689,20 +1739,20 @@ pub struct Ticket { pub resolution: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub enum TicketDoctorSeverity { Error, Warning, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketDoctorDiagnostic { pub severity: TicketDoctorSeverity, pub message: String, pub path: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TicketDoctorReport { pub diagnostics: Vec, } diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index dbcf5f3c3..4d5bb7ee4 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -11,10 +11,10 @@ use axum::extract::{DefaultBodyLimit, Extension, Path as AxumPath, Query, Reques use axum::http::header::{ CACHE_CONTROL, CONTENT_TYPE, ETAG, IF_NONE_MATCH, LOCATION, ORIGIN, SET_COOKIE, }; -use axum::http::{HeaderMap, Method, StatusCode, Uri}; +use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri}; use axum::middleware::{self, Next}; use axum::response::{IntoResponse, Response}; -use axum::routing::{delete, get, patch, post, put}; +use axum::routing::{delete, get, post, put}; use axum::{Json, Router}; use chrono::{Duration, SecondsFormat, Utc}; use config_source::ConfigTreeSnapshot; @@ -55,9 +55,9 @@ use server_api::{ RuntimeRemovalOperationState as ApiRuntimeRemovalOperationState, RuntimeTrustAuditAction, RuntimeTrustAuditEntry, RuntimeTrustConflictKind, RuntimeTrustConflictResponse, RuntimeTrustKeyRevealResponse, RuntimeTrustKeyState, RuntimeTrustKeyStatus, - TICKET_ORCHESTRATION_PLANS_QUERY_PATH, TICKET_RELATIONS_QUERY_PATH, UpdateRemoteRuntimeRequest, - UpdateWorkspaceMetadataRequest, WhoamiResponse, WorkerLaunchOptionsResponse, - WorkerLaunchProfileCandidate, WorkerLaunchRuntimeOption, WorkerLaunchWorkerSummary, + UpdateRemoteRuntimeRequest, UpdateWorkspaceMetadataRequest, WhoamiResponse, + WorkerLaunchOptionsResponse, WorkerLaunchProfileCandidate, WorkerLaunchRuntimeOption, + WorkerLaunchWorkerSummary, WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest, WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse, WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse, @@ -75,6 +75,8 @@ use server_api::{ WorkspaceSigningIdentityState, WorkspaceSummary, WorkspaceWorkerDiscoveryItem, WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject, }; +#[cfg(test)] +use server_api::{TICKET_ORCHESTRATION_PLANS_QUERY_PATH, TICKET_RELATIONS_QUERY_PATH}; use sha2::{Digest, Sha256}; use ticket::{ MarkdownText, NewTicketEvent, TicketBackend, TicketBodyReplacement, TicketEventKind, @@ -3637,12 +3639,52 @@ fn build_server_auth_router(api: ServerAuthApi) -> Router { .with_state(api) } +fn server_request_context( + actor: Option, + origin: Option, + headers: &HeaderMap, +) -> server_api::ServerRequestContext { + server_api::ServerRequestContext { + actor, + origin, + transport_headers: headers + .iter() + .map(|(name, value)| (name.as_str().to_string(), value.as_bytes().to_vec())) + .collect(), + } +} + +fn contract_request_headers( + context: &server_api::ServerRequestContext, +) -> std::result::Result { + let mut headers = HeaderMap::new(); + for (name, value) in &context.transport_headers { + let name = HeaderName::from_bytes(name.as_bytes()).map_err(|error| { + server_api::RepositoryApiError::new( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Internal Server Error", + format!("generated route retained an invalid request header name: {error}"), + Vec::new(), + ) + })?; + let value = HeaderValue::from_bytes(value).map_err(|error| { + server_api::RepositoryApiError::new( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Internal Server Error", + format!("generated route retained an invalid request header value: {error}"), + Vec::new(), + ) + })?; + headers.append(name, value); + } + Ok(headers) +} + async fn attach_server_origin_context(mut request: Request, next: Next) -> Response { let actor = request.extensions().get::().cloned(); let origin = request_origin(request.headers()); - request - .extensions_mut() - .insert(server_api::ServerRequestContext { actor, origin }); + let context = server_request_context(actor, origin, request.headers()); + request.extensions_mut().insert(context); next.run(request).await } @@ -3667,9 +3709,8 @@ async fn attach_server_request_context( } }; let origin = request_origin(request.headers()); - request - .extensions_mut() - .insert(server_api::ServerRequestContext { actor, origin }); + let context = server_request_context(actor, origin, request.headers()); + request.extensions_mut().insert(context); next.run(request).await } @@ -3824,6 +3865,149 @@ fn generated_workspace_contract_router(service: ServerApiContractService) -> Rou .merge(server_api::server_api_axum::repository_create( service.clone(), )) + .merge(server_api::server_api_axum::ticket_list_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_list(service.clone())) + .merge(server_api::server_api_axum::ticket_create_record( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_query(service.clone())) + .merge(server_api::server_api_axum::ticket_get_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_default_intake_ready_body(service.clone())) + .merge(server_api::server_api_axum::ticket_summary_search( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_doctor(service.clone())) + .merge(server_api::server_api_axum::ticket_relation_query( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_orchestration_plan_query(service.clone())) + .merge(server_api::server_api_axum::ticket_record_get( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_record_item_edit( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_dependency_check( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_thread_event_add( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_state_change_add( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_intake_summary_add( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_state_field_set( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_workflow_state_set( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_mark_ready_record( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_queue_record( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_list( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_show( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_open( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_readiness( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_thread( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_selector_repair( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_reviewer_child_register(service.clone())) + .merge( + server_api::server_api_axum::merge_request_review_capability_register(service.clone()), + ) + .merge(server_api::server_api_axum::merge_request_review_submit( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_review_revoke( + service.clone(), + )) + .merge(server_api::server_api_axum::merge_request_complete( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_close_record( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_relation_view( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_relation_record( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_relation_remove( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_orchestration_plan_record(service.clone())) + .merge(server_api::server_api_axum::ticket_get(service.clone())) + .merge(server_api::server_api_axum::ticket_edit(service.clone())) + .merge(server_api::server_api_axum::ticket_show(service.clone())) + .merge(server_api::server_api_axum::ticket_assignment_list( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_assignment_set( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_assignment_clear( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_implementation_cancel( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_state_transition( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_ready(service.clone())) + .merge(server_api::server_api_axum::ticket_event_append( + service.clone(), + )) + .merge(server_api::server_api_axum::ticket_queue(service.clone())) + .merge(server_api::server_api_axum::ticket_close(service.clone())) + .merge(server_api::server_api_axum::objective_list_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_list(service.clone())) + .merge(server_api::server_api_axum::objective_create( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_query( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_get_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_get(service.clone())) + .merge(server_api::server_api_axum::objective_edit(service.clone())) + .merge(server_api::server_api_axum::objective_show(service.clone())) + .merge(server_api::server_api_axum::objective_state_set( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_ticket_link( + service.clone(), + )) + .merge(server_api::server_api_axum::objective_ticket_unlink( + service.clone(), + )) .layer(middleware::from_fn_with_state( service.as_ref().clone(), attach_server_request_context, @@ -3836,6 +4020,12 @@ fn project_server_dto(value: &T) -> Result serde_json::from_value(value).map_err(|error| Error::RegistryInconsistency(error.to_string())) } +fn project_contract_dto( + value: &T, +) -> std::result::Result { + project_server_dto(value).map_err(|error| ApiError::from(error).into_repository_api_error()) +} + impl server_api::ServerApi for ServerApiContractService { async fn health( &self, @@ -4866,218 +5056,1060 @@ impl server_api::ServerApi for ServerApiContractService { .map(|(_status, Json(response))| response) .map_err(ApiError::into_repository_api_error) } + + async fn ticket_list_alias( + &self, + query: server_api::TicketListHttpQuery, + ) -> std::result::Result { + let query = TicketListQuery { + states: query.states, + limit: query.limit, + cursor: query.cursor, + }; + let Json(response) = list_tickets(State(self.workspace_api()?.clone()), Query(query)) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_list( + &self, + workspace_id: String, + query: server_api::TicketListHttpQuery, + ) -> std::result::Result { + let query = TicketListQuery { + states: query.states, + limit: query.limit, + cursor: query.cursor, + }; + let Json(response) = scoped_list_tickets( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_create_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::CreateTicketRecordRequest, + ) -> std::result::Result { + let Json(response) = scoped_create_ticket_record( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRecordRef(response)) + } + + async fn ticket_query( + &self, + workspace_id: String, + request: server_api::TicketQueryRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_query_tickets( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_get_alias( + &self, + id: String, + ) -> std::result::Result { + let Json(response) = get_ticket(State(self.workspace_api()?.clone()), AxumPath(id)) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_default_intake_ready_body( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::DefaultIntakeReadyBodyRequest, + ) -> std::result::Result { + let Json(response) = scoped_default_intake_ready_body( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Json(DefaultIntakeReadyBodyRequest { from: request.from }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TextResponse(response)) + } + + async fn ticket_summary_search( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + query: server_api::TicketSummarySearchQuery, + ) -> std::result::Result + { + let Json(response) = scoped_list_ticket_summaries( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Query(TicketSummarySearchQuery { state: query.state }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRecordSummaryList(response)) + } + + async fn ticket_doctor( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + ) -> std::result::Result { + let Json(response) = scoped_ticket_doctor( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketDoctorResponse(response)) + } + + async fn ticket_relation_query( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::TicketRelationSearchRequest, + ) -> std::result::Result + { + let Json(response) = scoped_query_ticket_relations( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Json(TicketRelationSearchRequest { + ticket: request.ticket, + kind: request.kind, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRelationRecordList(response)) + } + + async fn ticket_orchestration_plan_query( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::TicketOrchestrationPlanSearchRequest, + ) -> std::result::Result< + server_api::TicketOrchestrationPlanRecordList, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_query_ticket_orchestration_plans( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Json(TicketOrchestrationPlanSearchRequest { + ticket: request.ticket, + kind: request.kind, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketOrchestrationPlanRecordList(response)) + } + + async fn ticket_record_get( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + ) -> std::result::Result { + let Json(response) = scoped_get_ticket_record( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRecord(response)) + } + + async fn ticket_record_item_edit( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::EditTicketRecordItemRequest, + ) -> std::result::Result { + let Json(response) = scoped_edit_ticket_record_item( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRecord(response)) + } + + async fn ticket_dependency_check( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + ) -> std::result::Result< + server_api::TicketDependencyCheckResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_ticket_dependency_check( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketDependencyCheckResponse(response)) + } + + async fn ticket_thread_event_add( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketThreadEventRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_add_ticket_thread_event( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_state_change_add( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketStateChangeRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_add_ticket_state_change( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_intake_summary_add( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketIntakeSummaryRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_add_ticket_intake_summary( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_state_field_set( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + field: String, + request: server_api::TicketStateChangeRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_set_ticket_state_field( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id, field)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_workflow_state_set( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketStateChangeRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_set_ticket_workflow_state( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_mark_ready_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketMarkReadyRequest, + ) -> std::result::Result { + let Json(response) = scoped_mark_ticket_ready( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(TicketMarkReadyRequest { + operation_key: request.operation_key, + reason: request.reason, + intake_summary: request.intake_summary, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRecord(response)) + } + + async fn ticket_queue_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + ) -> std::result::Result { + let Json(response) = scoped_queue_ticket_record( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketQueueResponse(response)) + } + + async fn merge_request_list( + &self, + workspace_id: String, + query: server_api::MergeRequestListQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_list_merge_requests( + State(self.workspace_api()?.clone()), + AxumPath(workspace_id), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_show( + &self, + workspace_id: String, + merge_request_id: String, + query: server_api::MergeRequestThreadQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_show_merge_request( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, merge_request_id)), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_open( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::OpenMergeRequestRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_open_merge_request( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_readiness( + &self, + workspace_id: String, + id: String, + ) -> std::result::Result< + server_api::MergeRequestReadinessResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_merge_request_readiness( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_thread( + &self, + workspace_id: String, + id: String, + query: server_api::MergeRequestThreadQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_merge_request_thread( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response).map(server_api::MergeRequestThreadResponse) + } + + async fn merge_request_selector_repair( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::RepairMergeRequestSelectorRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_repair_merge_request_selector( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_reviewer_child_register( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::RegisterReviewerChildSessionRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + let request = project_contract_dto(&request)?; + scoped_register_reviewer_child_session( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath(workspace_id), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn merge_request_review_capability_register( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::RegisterMergeRequestReviewCapabilityRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + let request = project_contract_dto(&request)?; + scoped_register_merge_request_review_capability( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn merge_request_review_submit( + &self, + workspace_id: String, + id: String, + request: server_api::SubmitMergeRequestReviewRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_submit_merge_request_review( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_review_revoke( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::RevokeMergeRequestReviewRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_revoke_merge_request_review( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn merge_request_complete( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::CompleteMergeRequestRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_complete_merge_request( + State(self.workspace_api()?.clone()), + contract_request_headers(&context)?, + AxumPath((workspace_id, id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_close_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketCloseRecordRequest, + ) -> std::result::Result<(), server_api::RepositoryApiError> { + scoped_close_ticket_record( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(()) + } + + async fn ticket_relation_view( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + ) -> std::result::Result + { + let Json(response) = scoped_ticket_relation_view( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRelationRecordView(response)) + } + + async fn ticket_relation_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::CreateTicketRelationRequest, + ) -> std::result::Result { + let Json(response) = scoped_record_ticket_relation( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRelationRecord(response)) + } + + async fn ticket_relation_remove( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::TicketRelationRemoveRequest, + ) -> std::result::Result { + let Json(response) = scoped_remove_ticket_relation( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(TicketRelationRemoveRequest { + kind: request.kind, + target: request.target, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketRelationRecord(response)) + } + + async fn ticket_orchestration_plan_record( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + id: String, + request: server_api::CreateTicketOrchestrationPlanRequest, + ) -> std::result::Result< + server_api::TicketOrchestrationPlanRecord, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_record_ticket_orchestration_plan( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id)), + contract_request_headers(&context)?, + Json(request.0), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketOrchestrationPlanRecord(response)) + } + + async fn ticket_get( + &self, + workspace_id: String, + id: String, + ) -> std::result::Result { + let Json(response) = scoped_get_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_edit( + &self, + workspace_id: String, + id: String, + request: server_api::BrowserEditTicketRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_edit_ticket_item( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_show( + &self, + workspace_id: String, + id: String, + request: server_api::TicketShowRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_show_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_assignment_list( + &self, + workspace_id: String, + id: String, + ) -> std::result::Result< + server_api::TicketRoleAssignmentsResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_list_ticket_assignments( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_assignment_set( + &self, + workspace_id: String, + id: String, + role: String, + request: server_api::SetTicketRoleAssignmentRequest, + ) -> std::result::Result< + server_api::TicketRoleAssignmentMutationResponse, + server_api::RepositoryApiError, + > { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_set_ticket_assignment( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id, role)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_assignment_clear( + &self, + workspace_id: String, + id: String, + role: String, + query: server_api::ClearTicketRoleAssignmentQuery, + ) -> std::result::Result< + server_api::TicketRoleAssignmentMutationResponse, + server_api::RepositoryApiError, + > { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_clear_ticket_assignment( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, id, role)), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_implementation_cancel( + &self, + workspace_id: String, + id: String, + request: server_api::CancelTicketImplementationRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_cancel_ticket_implementation( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_state_transition( + &self, + workspace_id: String, + id: String, + request: server_api::BrowserTransitionTicketStateRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_transition_ticket_state( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_ready( + &self, + workspace_id: String, + id: String, + request: server_api::TicketMarkReadyRequest, + ) -> std::result::Result { + let request = TicketMarkReadyRequest { + operation_key: request.operation_key, + reason: request.reason, + intake_summary: request.intake_summary, + }; + let Json(response) = scoped_mark_ticket_ready_from_browser( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_event_append( + &self, + workspace_id: String, + id: String, + request: server_api::BrowserAppendTicketEventRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_append_ticket_event( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn ticket_queue( + &self, + workspace_id: String, + id: String, + request: server_api::BrowserQueueTicketRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_queue_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::TicketQueueResponse(response)) + } + + async fn ticket_close( + &self, + workspace_id: String, + id: String, + request: server_api::BrowserCloseTicketRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_close_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRecordPath { workspace_id, id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_list_alias( + &self, + query: server_api::ObjectiveListQuery, + ) -> std::result::Result + { + let Json(response) = list_objectives( + State(self.workspace_api()?.clone()), + Query(ObjectiveListQuery { limit: query.limit }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_list( + &self, + workspace_id: String, + query: server_api::ObjectiveListQuery, + ) -> std::result::Result + { + let Json(response) = scoped_list_objectives( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Query(ObjectiveListQuery { limit: query.limit }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_create( + &self, + workspace_id: String, + request: server_api::ObjectiveCreateRequest, + ) -> std::result::Result { + let Json(response) = scoped_create_objective( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_query( + &self, + workspace_id: String, + request: server_api::ObjectiveQueryRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_query_objectives( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_get_alias( + &self, + id: String, + ) -> std::result::Result { + let Json(response) = get_objective(State(self.workspace_api()?.clone()), AxumPath(id)) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_get( + &self, + workspace_id: String, + objective_id: String, + ) -> std::result::Result { + let Json(response) = scoped_get_objective( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectivePath { + workspace_id, + objective_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_edit( + &self, + workspace_id: String, + objective_id: String, + request: server_api::ObjectiveEditRequest, + ) -> std::result::Result { + let Json(response) = scoped_edit_objective( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectivePath { + workspace_id, + objective_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_show( + &self, + workspace_id: String, + objective_id: String, + request: server_api::ObjectiveShowRequest, + ) -> std::result::Result { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_show_objective( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectivePath { + workspace_id, + objective_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_state_set( + &self, + workspace_id: String, + objective_id: String, + request: server_api::ObjectiveStateRequest, + ) -> std::result::Result { + let Json(response) = scoped_set_objective_state( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectivePath { + workspace_id, + objective_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_ticket_link( + &self, + workspace_id: String, + objective_id: String, + request: server_api::ObjectiveLinkTicketRequest, + ) -> std::result::Result { + let Json(response) = scoped_link_objective_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectivePath { + workspace_id, + objective_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn objective_ticket_unlink( + &self, + workspace_id: String, + objective_id: String, + ticket_id: String, + ) -> std::result::Result { + let Json(response) = scoped_unlink_objective_ticket( + State(self.workspace_api()?.clone()), + AxumPath(ScopedObjectiveTicketPath { + workspace_id, + objective_id, + ticket_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } } fn build_inner_router(api: WorkspaceApi) -> Router { let contract_service = ServerApiContractService::Workspace(api.clone()); let auth = build_server_auth_router(ServerAuthApi::from(&api)) .merge(generated_auth_contract_router(contract_service.clone())); - let scoped_ticket_relations_query_path = - format!("/api/w/{{workspace_id}}{TICKET_RELATIONS_QUERY_PATH}"); - let scoped_ticket_orchestration_plans_query_path = - format!("/api/w/{{workspace_id}}{TICKET_ORCHESTRATION_PLANS_QUERY_PATH}"); let workspace = Router::new() - .route("/api/tickets", get(list_tickets)) - .route( - "/api/w/{workspace_id}/tickets", - get(scoped_list_tickets).post(scoped_create_ticket_record), - ) - .route( - "/api/w/{workspace_id}/tickets/query", - post(scoped_query_tickets), - ) - .route("/api/tickets/{id}", get(get_ticket)) - .route( - "/api/w/{workspace_id}/tickets/default-intake-ready-body", - post(scoped_default_intake_ready_body), - ) - .route( - "/api/w/{workspace_id}/tickets/search", - get(scoped_list_ticket_summaries), - ) - .route( - "/api/w/{workspace_id}/tickets/doctor", - get(scoped_ticket_doctor), - ) - .route( - scoped_ticket_relations_query_path.as_str(), - post(scoped_query_ticket_relations), - ) - .route( - scoped_ticket_orchestration_plans_query_path.as_str(), - post(scoped_query_ticket_orchestration_plans), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/record", - get(scoped_get_ticket_record), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/item", - patch(scoped_edit_ticket_record_item), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/dependency-check", - get(scoped_ticket_dependency_check), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/thread-events", - post(scoped_add_ticket_thread_event), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/state-changes", - post(scoped_add_ticket_state_change), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/intake-summaries", - post(scoped_add_ticket_intake_summary), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/state-fields/{field}", - post(scoped_set_ticket_state_field), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/workflow-state", - post(scoped_set_ticket_workflow_state), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/workflow/mark-ready", - post(scoped_mark_ticket_ready), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/workflow/queue", - post(scoped_queue_ticket_record), - ) - .route( - "/api/w/{workspace_id}/merge-requests", - get(scoped_list_merge_requests), - ) - .route( - "/api/w/{workspace_id}/merge-requests/{merge_request_id}", - get(scoped_show_merge_request), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request", - post(scoped_open_merge_request), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/readiness", - get(scoped_merge_request_readiness), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/thread", - get(scoped_merge_request_thread), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/repair-source", - post(scoped_repair_merge_request_selector), - ) - .route( - "/api/w/{workspace_id}/internal/reviewer-child-sessions", - post(scoped_register_reviewer_child_session), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/review-capabilities", - post(scoped_register_merge_request_review_capability), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/reviews", - post(scoped_submit_merge_request_review), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/reviews/revoke", - post(scoped_revoke_merge_request_review), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/merge-request/complete", - post(scoped_complete_merge_request), - ) - - .route( - "/api/w/{workspace_id}/tickets/{id}/workflow/close", - post(scoped_close_ticket_record), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/relation-view", - get(scoped_ticket_relation_view), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/relations", - post(scoped_record_ticket_relation).delete(scoped_remove_ticket_relation), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/orchestration-plans", - post(scoped_record_ticket_orchestration_plan), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}", - get(scoped_get_ticket).patch(scoped_edit_ticket_item), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/show", - post(scoped_show_ticket), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/assignments", - get(scoped_list_ticket_assignments), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/assignments/{role}", - put(scoped_set_ticket_assignment).delete(scoped_clear_ticket_assignment), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/implementation-cancellations", - post(scoped_cancel_ticket_implementation), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/state", - post(scoped_transition_ticket_state), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/ready", - post(scoped_mark_ticket_ready_from_browser), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/events", - post(scoped_append_ticket_event), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/queue", - post(scoped_queue_ticket), - ) - .route( - "/api/w/{workspace_id}/tickets/{id}/close", - post(scoped_close_ticket), - ) - .route("/api/objectives", get(list_objectives)) - .route( - "/api/w/{workspace_id}/objectives", - get(scoped_list_objectives).post(scoped_create_objective), - ) - .route( - "/api/w/{workspace_id}/objectives/query", - post(scoped_query_objectives), - ) - .route("/api/objectives/{id}", get(get_objective)) - .route( - "/api/w/{workspace_id}/objectives/{objective_id}", - get(scoped_get_objective).patch(scoped_edit_objective), - ) - .route( - "/api/w/{workspace_id}/objectives/{objective_id}/show", - post(scoped_show_objective), - ) - .route( - "/api/w/{workspace_id}/objectives/{objective_id}/state", - post(scoped_set_objective_state), - ) - .route( - "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links", - post(scoped_link_objective_ticket), - ) - .route( - "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links/{ticket_id}", - delete(scoped_unlink_objective_ticket), - ) .route( "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", post(scoped_probe_repository_ssh_connection) diff --git a/openapi/server-api.json b/openapi/server-api.json index 8b60d309c..b2eaccb9b 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -1,6 +1,36 @@ { "components": { "schemas": { + "AcceptedOrchestrationPlan": { + "additionalProperties": false, + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "role_plan": { + "type": [ + "string", + "null" + ] + }, + "summary": { + "type": "string" + }, + "worktree": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "summary" + ], + "type": "object" + }, "ActorAuthMethod": { "description": "Authentication method that established the current request actor.", "enum": [ @@ -15,6 +45,18 @@ }, "type": "array" }, + "Array_of_MergeRequestThreadEvent": { + "items": { + "$ref": "#/components/schemas/MergeRequestThreadEvent" + }, + "type": "array" + }, + "Array_of_OrchestrationPlanRecord": { + "items": { + "$ref": "#/components/schemas/OrchestrationPlanRecord" + }, + "type": "array" + }, "Array_of_RepositorySshCredential": { "items": { "$ref": "#/components/schemas/RepositorySshCredential" @@ -27,6 +69,18 @@ }, "type": "array" }, + "Array_of_TicketRelation": { + "items": { + "$ref": "#/components/schemas/TicketRelation" + }, + "type": "array" + }, + "Array_of_TicketSummary": { + "items": { + "$ref": "#/components/schemas/TicketSummary" + }, + "type": "array" + }, "AuditEvent": { "oneOf": [ { @@ -191,6 +245,204 @@ ], "type": "object" }, + "BrowserAppendTicketEventRequest": { + "additionalProperties": false, + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/BrowserTicketThreadRole" + } + }, + "required": [ + "role", + "body" + ], + "type": "object" + }, + "BrowserCloseTicketRequest": { + "additionalProperties": false, + "properties": { + "resolution": { + "type": "string" + } + }, + "required": [ + "resolution" + ], + "type": "object" + }, + "BrowserEditTicketRequest": { + "additionalProperties": false, + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": [ + "string", + "null" + ] + }, + "new_string": { + "type": [ + "string", + "null" + ] + }, + "old_string": { + "type": [ + "string", + "null" + ] + }, + "replace_all": { + "default": false, + "type": "boolean" + }, + "target": { + "anyOf": [ + { + "$ref": "#/components/schemas/BrowserTicketTargetEdit" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "BrowserQueueTicketRequest": { + "additionalProperties": false, + "type": "object" + }, + "BrowserTicketTargetEdit": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "action": { + "const": "set", + "type": "string" + }, + "ref_selector": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": "string" + } + }, + "required": [ + "action", + "repository_key" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "action": { + "const": "clear", + "type": "string" + } + }, + "required": [ + "action" + ], + "type": "object" + } + ] + }, + "BrowserTicketThreadRole": { + "enum": [ + "comment", + "plan", + "decision", + "implementation_report" + ], + "type": "string" + }, + "BrowserTicketWorkflowState": { + "enum": [ + "planning", + "ready", + "queued", + "inprogress", + "done", + "closed" + ], + "type": "string" + }, + "BrowserTransitionTicketStateRequest": { + "additionalProperties": false, + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": [ + "string", + "null" + ] + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "state": { + "$ref": "#/components/schemas/BrowserTicketWorkflowState" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + "CancelTicketImplementationRequest": { + "additionalProperties": false, + "properties": { + "assignment_id": { + "type": "string" + }, + "operation_id": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "operation_id", + "assignment_id", + "reason" + ], + "type": "object" + }, "CandidateKind": { "description": "Candidate kinds that extract is allowed to stage.", "enum": [ @@ -203,6 +455,24 @@ ], "type": "string" }, + "ClearTicketRoleAssignmentQuery": { + "additionalProperties": false, + "properties": { + "assignment_id": { + "type": [ + "string", + "null" + ] + }, + "operation_id": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, "CompiledFlowDefinition": { "additionalProperties": false, "properties": { @@ -287,6 +557,38 @@ ], "type": "object" }, + "CompleteMergeRequestRequest": { + "additionalProperties": false, + "properties": { + "approval_event_id": { + "type": "string" + }, + "operation_id": { + "type": "string" + }, + "resolution": { + "$ref": "#/components/schemas/ConflictResolution" + }, + "strategy": { + "$ref": "#/components/schemas/MergeStrategy" + }, + "target_ref_after": { + "type": "string" + }, + "target_ref_before": { + "type": "string" + } + }, + "required": [ + "operation_id", + "approval_event_id", + "target_ref_before", + "target_ref_after", + "strategy", + "resolution" + ], + "type": "object" + }, "ConfigCommitRequest": { "additionalProperties": false, "description": "Compare-and-swap request for applying a complete set of source-tree changes.", @@ -542,6 +844,14 @@ ], "type": "object" }, + "ConflictResolution": { + "enum": [ + "none", + "clean", + "conflicts_resolved" + ], + "type": "string" + }, "ConsolidationAudit": { "properties": { "consumed_staging_ids": { @@ -658,6 +968,18 @@ ], "type": "object" }, + "DefaultIntakeReadyBodyRequest": { + "additionalProperties": false, + "properties": { + "from": { + "type": "string" + } + }, + "required": [ + "from" + ], + "type": "object" + }, "DeleteRepositorySshCredentialRequest": { "additionalProperties": false, "properties": { @@ -696,6 +1018,39 @@ ], "type": "object" }, + "DerivedTicketRelation": { + "properties": { + "at": { + "type": "string" + }, + "author": { + "type": "string" + }, + "forward_kind": { + "$ref": "#/components/schemas/TicketRelationKind" + }, + "inverse_kind": { + "type": "string" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "source_ticket": { + "type": "string" + } + }, + "required": [ + "source_ticket", + "inverse_kind", + "forward_kind", + "author", + "at" + ], + "type": "object" + }, "DeviceAccessTokenType": { "enum": [ "Bearer" @@ -976,6 +1331,29 @@ ], "type": "string" }, + "ExtensibleTicketStatus": { + "oneOf": [ + { + "enum": [ + "Open", + "Closed" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "Other": { + "type": "string" + } + }, + "required": [ + "Other" + ], + "type": "object" + } + ] + }, "ExtractAudit": { "properties": { "entry_range": { @@ -1084,6 +1462,15 @@ }, "type": "object" }, + "FindingSeverity": { + "enum": [ + "blocker", + "major", + "minor", + "note" + ], + "type": "string" + }, "FlowSourceKind": { "enum": [ "builtin", @@ -1262,6 +1649,26 @@ ], "type": "object" }, + "InvalidProjectRecord": { + "additionalProperties": false, + "description": "One malformed project record reported by a bounded list projection.", + "properties": { + "label": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "label", + "reason" + ], + "type": "object" + }, + "MarkdownText": { + "type": "string" + }, "MemoryAppendAuditOperation": { "properties": { "event": { @@ -2205,6 +2612,671 @@ ], "type": "object" }, + "MergeEvent": { + "additionalProperties": false, + "properties": { + "approval_event_id": { + "type": "string" + }, + "approved_source_ref": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "merged_by": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "operation_id": { + "type": "string" + }, + "resolution": { + "$ref": "#/components/schemas/ConflictResolution" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "strategy": { + "$ref": "#/components/schemas/MergeStrategy" + }, + "target_ref_after": { + "type": "string" + }, + "target_ref_before": { + "type": "string" + } + }, + "required": [ + "event_id", + "sequence", + "operation_id", + "approval_event_id", + "approved_source_ref", + "target_ref_before", + "target_ref_after", + "strategy", + "resolution", + "merged_by", + "created_at" + ], + "type": "object" + }, + "MergeRequestDetailResponse": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "linked_tickets": { + "items": { + "$ref": "#/components/schemas/MergeRequestLinkedTicketResponse" + }, + "type": "array" + }, + "merge_request_id": { + "type": "string" + }, + "repository_key": { + "type": "string" + }, + "selector_from": { + "type": [ + "string", + "null" + ] + }, + "selector_to": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/MergeRequestRefResponse" + }, + "state": { + "$ref": "#/components/schemas/MergeRequestState" + }, + "target": { + "$ref": "#/components/schemas/MergeRequestRefResponse" + }, + "thread": { + "items": { + "$ref": "#/components/schemas/MergeRequestThreadEvent" + }, + "type": "array" + }, + "ticket_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "merge_request_id", + "repository_key", + "state", + "selector_to", + "ticket_ids", + "created_at", + "updated_at", + "thread", + "source", + "target", + "linked_tickets" + ], + "type": "object" + }, + "MergeRequestLinkedTicketResponse": { + "additionalProperties": false, + "properties": { + "key": { + "type": [ + "string", + "null" + ] + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "ticket_id" + ], + "type": "object" + }, + "MergeRequestListItem": { + "additionalProperties": false, + "properties": { + "ref_diagnostics": { + "items": { + "$ref": "#/components/schemas/MergeRequestRefDiagnostic" + }, + "type": "array" + }, + "summary": { + "$ref": "#/components/schemas/TicketMergeRequestSummary" + }, + "thread_event_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "ticket_ids": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "summary", + "ticket_ids", + "thread_event_count" + ], + "type": "object" + }, + "MergeRequestListQuery": { + "additionalProperties": false, + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "repository_key": { + "type": [ + "string", + "null" + ] + }, + "selector_from": { + "type": [ + "string", + "null" + ] + }, + "selector_to": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": [ + "string", + "null" + ] + }, + "ticket_ref": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "MergeRequestListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/MergeRequestListItem" + }, + "type": "array" + }, + "next_cursor": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "MergeRequestReadinessResponse": { + "additionalProperties": false, + "properties": { + "blockers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ready": { + "type": "boolean" + }, + "review": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReviewEvent" + }, + { + "type": "null" + } + ] + }, + "subject_ref": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "ready", + "blockers" + ], + "type": "object" + }, + "MergeRequestRefDiagnostic": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "MergeRequestRefResponse": { + "additionalProperties": false, + "properties": { + "diagnostic": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeRequestRefDiagnostic" + }, + { + "type": "null" + } + ] + }, + "observed_at": { + "type": "string" + }, + "ref": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + } + }, + "required": [ + "status", + "observed_at" + ], + "type": "object" + }, + "MergeRequestState": { + "enum": [ + "open", + "merged", + "closed" + ], + "type": "string" + }, + "MergeRequestThreadEvent": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "kind": { + "const": "review_requested", + "type": "string" + }, + "requested_by": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "reviewer": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "subject_ref", + "requested_by", + "reviewer", + "created_at" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "body": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "decision": { + "$ref": "#/components/schemas/ReviewDecision" + }, + "event_id": { + "type": "string" + }, + "findings": { + "items": { + "$ref": "#/components/schemas/ReviewFinding" + }, + "type": "array" + }, + "kind": { + "const": "review", + "type": "string" + }, + "request_event_id": { + "type": "string" + }, + "reviewer": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "request_event_id", + "subject_ref", + "decision", + "body", + "findings", + "reviewer", + "created_at" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "kind": { + "const": "review_revoked", + "type": "string" + }, + "reason": { + "type": "string" + }, + "review_event_id": { + "type": "string" + }, + "revoked_by": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "review_event_id", + "subject_ref", + "reason", + "revoked_by", + "created_at" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "kind": { + "const": "review_cancelled", + "type": "string" + }, + "reason": { + "type": "string" + }, + "request_event_id": { + "type": "string" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "request_event_id", + "subject_ref", + "reason", + "created_at" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "author": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "body": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "kind": { + "const": "comment", + "type": "string" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "body", + "author", + "created_at" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "approval_event_id": { + "type": "string" + }, + "approved_source_ref": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "kind": { + "const": "merge", + "type": "string" + }, + "merged_by": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "operation_id": { + "type": "string" + }, + "resolution": { + "$ref": "#/components/schemas/ConflictResolution" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "strategy": { + "$ref": "#/components/schemas/MergeStrategy" + }, + "target_ref_after": { + "type": "string" + }, + "target_ref_before": { + "type": "string" + } + }, + "required": [ + "kind", + "event_id", + "sequence", + "operation_id", + "approval_event_id", + "approved_source_ref", + "target_ref_before", + "target_ref_after", + "strategy", + "resolution", + "merged_by", + "created_at" + ], + "type": "object" + } + ] + }, + "MergeRequestThreadQuery": { + "additionalProperties": false, + "properties": { + "after": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "MergeRequestWorkerIdentity": { + "additionalProperties": false, + "properties": { + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "runtime_id", + "worker_id" + ], + "type": "object" + }, + "MergeStrategy": { + "enum": [ + "fast_forward", + "merge" + ], + "type": "string" + }, "ModelAudit": { "properties": { "model_id": { @@ -2228,6 +3300,760 @@ }, "type": "object" }, + "NewOrchestrationPlanRecord": { + "properties": { + "accepted_plan": { + "anyOf": [ + { + "$ref": "#/components/schemas/AcceptedOrchestrationPlan" + }, + { + "type": "null" + } + ] + }, + "author": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "$ref": "#/components/schemas/OrchestrationPlanKind" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "related_ticket": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "NewTicket": { + "properties": { + "assignee": { + "type": [ + "string", + "null" + ] + }, + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "kind": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "priority": { + "type": "string" + }, + "queued_at": { + "type": [ + "string", + "null" + ] + }, + "queued_by": { + "type": [ + "string", + "null" + ] + }, + "readiness": { + "type": [ + "string", + "null" + ] + }, + "ref_selector": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": [ + "string", + "null" + ] + }, + "risk_flags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "slug": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": "string" + }, + "workflow_state": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketWorkflowState" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "title", + "kind", + "priority", + "labels", + "body", + "risk_flags" + ], + "type": "object" + }, + "NewTicketEvent": { + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "kind": { + "$ref": "#/components/schemas/TicketEventKind" + }, + "references": { + "items": { + "$ref": "#/components/schemas/TicketReference" + }, + "type": "array" + } + }, + "required": [ + "kind", + "body", + "references" + ], + "type": "object" + }, + "NewTicketRelation": { + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "$ref": "#/components/schemas/TicketRelationKind" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + "ObjectiveCreateRequest": { + "additionalProperties": false, + "properties": { + "body_md": { + "default": "", + "type": "string" + }, + "linked_tickets": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "state": { + "default": "active", + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "title" + ], + "type": "object" + }, + "ObjectiveDetail": { + "additionalProperties": false, + "properties": { + "body": { + "type": "string" + }, + "body_truncated": { + "type": "boolean" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "event_page": { + "$ref": "#/components/schemas/QueryPage" + }, + "events": { + "items": { + "$ref": "#/components/schemas/ObjectiveEventDetail" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "linked_ticket_summaries": { + "items": { + "$ref": "#/components/schemas/ObjectiveLinkedTicketSummary" + }, + "type": "array" + }, + "linked_tickets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "record_source": { + "type": "string" + }, + "resource_key": { + "type": "string" + }, + "resources": { + "items": { + "$ref": "#/components/schemas/ObjectiveResourceSummary" + }, + "type": "array" + }, + "revision": { + "type": "string" + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "revision", + "linked_tickets", + "linked_ticket_summaries", + "resources", + "body", + "body_truncated", + "events", + "event_page", + "record_source" + ], + "type": "object" + }, + "ObjectiveEditRequest": { + "additionalProperties": false, + "properties": { + "new_string": { + "type": [ + "string", + "null" + ] + }, + "old_string": { + "type": [ + "string", + "null" + ] + }, + "replace_all": { + "default": false, + "type": "boolean" + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ObjectiveEventDetail": { + "additionalProperties": false, + "properties": { + "body": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "event_ref": { + "type": "string" + }, + "kind": { + "type": "string" + } + }, + "required": [ + "event_ref", + "kind", + "created_at" + ], + "type": "object" + }, + "ObjectiveLinkSummary": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "resource_key": { + "type": "string" + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "resource_key", + "title", + "state" + ], + "type": "object" + }, + "ObjectiveLinkTicketRequest": { + "additionalProperties": false, + "properties": { + "ticket_id": { + "type": "string" + } + }, + "required": [ + "ticket_id" + ], + "type": "object" + }, + "ObjectiveLinkedTicketSummary": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "resource_key": { + "type": "string" + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "resource_key", + "title", + "state" + ], + "type": "object" + }, + "ObjectiveListQuery": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "ObjectiveListResponse": { + "additionalProperties": false, + "properties": { + "invalid_records": { + "items": { + "$ref": "#/components/schemas/InvalidProjectRecord" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/ObjectiveSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": "integer" + }, + "record_authority": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "invalid_records", + "record_authority" + ], + "type": "object" + }, + "ObjectiveQueryItem": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "linked_ticket_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "linked_ticket_keys": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_tickets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "matched_fields": { + "items": { + "type": "string" + }, + "type": "array" + }, + "resource_key": { + "type": "string" + }, + "snippet": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "matched_fields", + "linked_ticket_count", + "linked_tickets", + "linked_ticket_keys" + ], + "type": "object" + }, + "ObjectiveQueryRequest": { + "additionalProperties": false, + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "linked_ticket_id": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "sort": { + "type": [ + "string", + "null" + ] + }, + "states": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_after": { + "type": [ + "string", + "null" + ] + }, + "updated_before": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ObjectiveQueryResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ObjectiveQueryItem" + }, + "type": "array" + }, + "page": { + "$ref": "#/components/schemas/QueryPage" + }, + "record_authority": { + "type": "string" + } + }, + "required": [ + "items", + "page", + "record_authority" + ], + "type": "object" + }, + "ObjectiveResourceSummary": { + "additionalProperties": false, + "properties": { + "bytes": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "media_type": { + "type": [ + "string", + "null" + ] + }, + "path": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "path", + "bytes", + "updated_at" + ], + "type": "object" + }, + "ObjectiveShowRequest": { + "additionalProperties": false, + "properties": { + "event_cursor": { + "type": [ + "string", + "null" + ] + }, + "event_limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "ObjectiveStateRequest": { + "additionalProperties": false, + "properties": { + "state": { + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + "ObjectiveSummary": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "linked_tickets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "record_source": { + "type": "string" + }, + "resource_key": { + "type": "string" + }, + "state": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "summary", + "linked_tickets", + "record_source" + ], + "type": "object" + }, + "OpenMergeRequestRequest": { + "additionalProperties": false, + "properties": { + "repository_key": { + "type": "string" + }, + "selector_from": { + "type": "string" + }, + "selector_to": { + "type": "string" + }, + "summary": { + "default": "", + "type": "string" + } + }, + "required": [ + "repository_key", + "selector_from", + "selector_to" + ], + "type": "object" + }, "OperationCounts": { "properties": { "delete": { @@ -2275,6 +4101,69 @@ }, "type": "object" }, + "OrchestrationPlanKind": { + "enum": [ + "before", + "after", + "blocked_by", + "blocks", + "conflicts_with", + "do_not_parallelize", + "waiting_capacity_note", + "accepted_plan" + ], + "type": "string" + }, + "OrchestrationPlanRecord": { + "additionalProperties": false, + "properties": { + "accepted_plan": { + "anyOf": [ + { + "$ref": "#/components/schemas/AcceptedOrchestrationPlan" + }, + { + "type": "null" + } + ] + }, + "at": { + "type": "string" + }, + "author": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/OrchestrationPlanKind" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "related_ticket": { + "type": [ + "string", + "null" + ] + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "id", + "ticket_id", + "kind", + "author", + "at" + ], + "type": "object" + }, "PasskeyLoginOptionsRequest": { "additionalProperties": false, "properties": { @@ -2410,6 +4299,62 @@ ], "type": "object" }, + "PublicMergeRequest": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "merge_request_id": { + "type": "string" + }, + "repository_key": { + "type": "string" + }, + "selector_from": { + "type": [ + "string", + "null" + ] + }, + "selector_to": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/MergeRequestState" + }, + "thread": { + "items": { + "$ref": "#/components/schemas/MergeRequestThreadEvent" + }, + "type": "array" + }, + "ticket_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "merge_request_id", + "repository_key", + "state", + "selector_to", + "ticket_ids", + "created_at", + "updated_at", + "thread" + ], + "type": "object" + }, "PutFlowRequest": { "additionalProperties": false, "properties": { @@ -2467,6 +4412,55 @@ ], "type": "object" }, + "QueryPage": { + "additionalProperties": false, + "properties": { + "has_more": { + "type": "boolean" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "next_cursor": { + "type": [ + "string", + "null" + ] + }, + "returned": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "sort": { + "type": "string" + }, + "source_limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "source_truncated": { + "type": "boolean" + } + }, + "required": [ + "limit", + "returned", + "has_more", + "sort", + "source_truncated" + ], + "type": "object" + }, "RecordOperationAudit": { "properties": { "after_hash": { @@ -2564,6 +4558,54 @@ ], "type": "object" }, + "RegisterMergeRequestReviewCapabilityRequest": { + "additionalProperties": false, + "properties": { + "capability_token": { + "type": "string" + }, + "child_session_id": { + "type": "string" + } + }, + "required": [ + "child_session_id", + "capability_token" + ], + "type": "object" + }, + "RegisterReviewerChildSessionRequest": { + "additionalProperties": false, + "properties": { + "child_session_id": { + "type": "string" + } + }, + "required": [ + "child_session_id" + ], + "type": "object" + }, + "RepairMergeRequestSelectorRequest": { + "additionalProperties": false, + "properties": { + "explicit_confirmation": { + "type": "boolean" + }, + "reason": { + "type": "string" + }, + "selector_from": { + "type": "string" + } + }, + "required": [ + "selector_from", + "reason", + "explicit_confirmation" + ], + "type": "object" + }, "RepositoryAccessMode": { "enum": [ "read_only", @@ -3114,6 +5156,158 @@ ], "type": "object" }, + "ReviewDecision": { + "enum": [ + "approve", + "request_changes" + ], + "type": "string" + }, + "ReviewEvent": { + "additionalProperties": false, + "properties": { + "body": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "decision": { + "$ref": "#/components/schemas/ReviewDecision" + }, + "event_id": { + "type": "string" + }, + "findings": { + "items": { + "$ref": "#/components/schemas/ReviewFinding" + }, + "type": "array" + }, + "request_event_id": { + "type": "string" + }, + "reviewer": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "event_id", + "sequence", + "request_event_id", + "subject_ref", + "decision", + "body", + "findings", + "reviewer", + "created_at" + ], + "type": "object" + }, + "ReviewFinding": { + "additionalProperties": false, + "properties": { + "body": { + "type": "string" + }, + "code": { + "type": [ + "string", + "null" + ] + }, + "line": { + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "severity": { + "$ref": "#/components/schemas/FindingSeverity" + } + }, + "required": [ + "severity", + "body" + ], + "type": "object" + }, + "ReviewRevokedEvent": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "review_event_id": { + "type": "string" + }, + "revoked_by": { + "$ref": "#/components/schemas/MergeRequestWorkerIdentity" + }, + "sequence": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "subject_ref": { + "type": "string" + } + }, + "required": [ + "event_id", + "sequence", + "review_event_id", + "subject_ref", + "reason", + "revoked_by", + "created_at" + ], + "type": "object" + }, + "RevokeMergeRequestReviewRequest": { + "additionalProperties": false, + "properties": { + "explicit_confirmation": { + "type": "boolean" + }, + "reason": { + "type": "string" + }, + "review_event_id": { + "type": "string" + } + }, + "required": [ + "review_event_id", + "reason", + "explicit_confirmation" + ], + "type": "object" + }, "RotateRepositorySshCredentialRequest": { "additionalProperties": false, "properties": { @@ -3150,6 +5344,28 @@ "minimum": 0, "type": "integer" }, + "SetTicketRoleAssignmentRequest": { + "additionalProperties": false, + "properties": { + "expected_assignment_id": { + "type": [ + "string", + "null" + ] + }, + "operation_id": { + "type": "string" + }, + "principal": { + "$ref": "#/components/schemas/TicketAssignmentPrincipal" + } + }, + "required": [ + "operation_id", + "principal" + ], + "type": "object" + }, "SkillActivationResponse": { "additionalProperties": false, "properties": { @@ -3610,6 +5826,2303 @@ ], "type": "object" }, + "SubmitMergeRequestReviewRequest": { + "additionalProperties": false, + "properties": { + "body": { + "default": "", + "type": "string" + }, + "capability_token": { + "type": "string" + }, + "decision": { + "$ref": "#/components/schemas/ReviewDecision" + }, + "findings": { + "default": [], + "items": { + "$ref": "#/components/schemas/ReviewFinding" + }, + "type": "array" + } + }, + "required": [ + "capability_token", + "decision" + ], + "type": "object" + }, + "Ticket": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/components/schemas/TicketArtifactRef" + }, + "type": "array" + }, + "document": { + "$ref": "#/components/schemas/TicketDocument" + }, + "events": { + "items": { + "$ref": "#/components/schemas/TicketEvent" + }, + "type": "array" + }, + "meta": { + "$ref": "#/components/schemas/TicketMeta" + }, + "relations": { + "$ref": "#/components/schemas/TicketRelationView" + }, + "resolution": { + "anyOf": [ + { + "$ref": "#/components/schemas/MarkdownText" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "meta", + "document", + "events", + "artifacts", + "relations" + ], + "type": "object" + }, + "TicketActionEligibility": { + "additionalProperties": false, + "properties": { + "blockers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "can_assign_orchestrator": { + "type": "boolean" + }, + "can_queue": { + "type": "boolean" + }, + "can_start_manual_coder": { + "type": "boolean" + }, + "can_unassign_orchestrator": { + "type": "boolean" + }, + "queue_tickets": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "can_assign_orchestrator", + "can_unassign_orchestrator", + "can_queue", + "can_start_manual_coder", + "queue_tickets", + "blockers" + ], + "type": "object" + }, + "TicketArtifactRef": { + "properties": { + "relative_path": { + "description": "Path relative to the ticket's `artifacts/` directory.", + "type": "string" + } + }, + "required": [ + "relative_path" + ], + "type": "object" + }, + "TicketAssignmentPrincipal": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "kind": { + "const": "user", + "type": "string" + } + }, + "required": [ + "kind", + "account_id" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "worker", + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "kind", + "runtime_id", + "worker_id" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "agent_key": { + "type": "string" + }, + "kind": { + "const": "workspace_agent", + "type": "string" + } + }, + "required": [ + "kind", + "agent_key" + ], + "type": "object" + } + ] + }, + "TicketAssignmentRole": { + "enum": [ + "orchestrator", + "coder", + "owner", + "contributor" + ], + "type": "string" + }, + "TicketAssignmentSummary": { + "additionalProperties": false, + "properties": { + "assignment_id": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + }, + "worker_resource_key": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "assignment_id", + "runtime_id", + "worker_id" + ], + "type": "object" + }, + "TicketBodyReplacement": { + "properties": { + "new_string": { + "type": "string" + }, + "old_string": { + "type": "string" + }, + "replace_all": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "old_string", + "new_string" + ], + "type": "object" + }, + "TicketDependencyCheck": { + "properties": { + "blockers": { + "items": { + "$ref": "#/components/schemas/TicketRelationBlocker" + }, + "type": "array" + }, + "queue_guard": { + "$ref": "#/components/schemas/TicketQueueGuard" + }, + "queue_tickets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "recommended_action": { + "$ref": "#/components/schemas/TicketWorkspaceNextAction" + }, + "ticket": { + "$ref": "#/components/schemas/TicketSummary" + } + }, + "required": [ + "ticket", + "blockers", + "queue_guard", + "queue_tickets", + "recommended_action" + ], + "type": "object" + }, + "TicketDetail": { + "additionalProperties": false, + "properties": { + "action_eligibility": { + "$ref": "#/components/schemas/TicketActionEligibility" + }, + "artifact_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "artifacts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "assignment_diagnostics": { + "items": { + "type": "string" + }, + "type": "array" + }, + "assignments": { + "items": { + "$ref": "#/components/schemas/TicketRoleAssignmentSummary" + }, + "type": "array" + }, + "body": { + "type": "string" + }, + "body_truncated": { + "type": "boolean" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "current_coder": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketAssignmentSummary" + }, + { + "type": "null" + } + ] + }, + "event_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "event_page": { + "$ref": "#/components/schemas/QueryPage" + }, + "events": { + "items": { + "$ref": "#/components/schemas/TicketEventDetail" + }, + "type": "array" + }, + "evidence": { + "$ref": "#/components/schemas/TicketEvidenceSummary" + }, + "id": { + "type": "string" + }, + "implementation_reports": { + "items": { + "$ref": "#/components/schemas/TicketEvidenceEvent" + }, + "type": "array" + }, + "item_revision": { + "type": "string" + }, + "linked_objectives": { + "items": { + "$ref": "#/components/schemas/ObjectiveLinkSummary" + }, + "type": "array" + }, + "merge_request": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketMergeRequestSummary" + }, + { + "type": "null" + } + ] + }, + "priority": { + "type": "string" + }, + "queued_at": { + "type": [ + "string", + "null" + ] + }, + "queued_by": { + "type": [ + "string", + "null" + ] + }, + "readiness": { + "type": [ + "string", + "null" + ] + }, + "record_source": { + "type": "string" + }, + "ref_selector": { + "type": [ + "string", + "null" + ] + }, + "relations": { + "$ref": "#/components/schemas/TicketDetailRelationView" + }, + "repository_key": { + "type": [ + "string", + "null" + ] + }, + "resolution": { + "type": [ + "string", + "null" + ] + }, + "resource_key": { + "type": "string" + }, + "risk_flags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "priority", + "item_revision", + "risk_flags", + "body", + "body_truncated", + "event_count", + "events", + "event_page", + "artifact_count", + "artifacts", + "relations", + "linked_objectives", + "implementation_reports", + "assignments", + "assignment_diagnostics", + "action_eligibility", + "evidence", + "record_source" + ], + "type": "object" + }, + "TicketDetailDerivedRelation": { + "additionalProperties": false, + "properties": { + "at": { + "type": "string" + }, + "author": { + "type": "string" + }, + "forward_kind": { + "type": "string" + }, + "inverse_kind": { + "type": "string" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "source_resource_key": { + "type": [ + "string", + "null" + ] + }, + "source_ticket": { + "type": "string" + } + }, + "required": [ + "source_ticket", + "inverse_kind", + "forward_kind", + "author", + "at" + ], + "type": "object" + }, + "TicketDetailRelation": { + "additionalProperties": false, + "properties": { + "at": { + "type": "string" + }, + "author": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": "string" + }, + "target_resource_key": { + "type": [ + "string", + "null" + ] + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "ticket_id", + "kind", + "target", + "author", + "at" + ], + "type": "object" + }, + "TicketDetailRelationBlocker": { + "additionalProperties": false, + "properties": { + "blocking_resource_key": { + "type": [ + "string", + "null" + ] + }, + "blocking_state": { + "type": "string" + }, + "blocking_ticket": { + "type": "string" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "reason_kind": { + "type": "string" + }, + "relation_kind": { + "type": "string" + } + }, + "required": [ + "blocking_ticket", + "reason_kind", + "relation_kind", + "blocking_state" + ], + "type": "object" + }, + "TicketDetailRelationNotice": { + "additionalProperties": false, + "properties": { + "kind": { + "type": "string" + }, + "message": { + "type": "string" + }, + "related_ticket": { + "type": "string" + } + }, + "required": [ + "related_ticket", + "kind", + "message" + ], + "type": "object" + }, + "TicketDetailRelationView": { + "additionalProperties": false, + "properties": { + "blockers": { + "items": { + "$ref": "#/components/schemas/TicketDetailRelationBlocker" + }, + "type": "array" + }, + "incoming": { + "items": { + "$ref": "#/components/schemas/TicketDetailDerivedRelation" + }, + "type": "array" + }, + "notices": { + "items": { + "$ref": "#/components/schemas/TicketDetailRelationNotice" + }, + "type": "array" + }, + "outgoing": { + "items": { + "$ref": "#/components/schemas/TicketDetailRelation" + }, + "type": "array" + } + }, + "required": [ + "outgoing", + "incoming", + "blockers", + "notices" + ], + "type": "object" + }, + "TicketDoctorDiagnostic": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "severity": { + "$ref": "#/components/schemas/TicketDoctorSeverity" + } + }, + "required": [ + "severity", + "message" + ], + "type": "object" + }, + "TicketDoctorReport": { + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/TicketDoctorDiagnostic" + }, + "type": "array" + } + }, + "required": [ + "diagnostics" + ], + "type": "object" + }, + "TicketDoctorSeverity": { + "enum": [ + "Error", + "Warning" + ], + "type": "string" + }, + "TicketDocument": { + "properties": { + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "raw_frontmatter": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "body", + "raw_frontmatter" + ], + "type": "object" + }, + "TicketEvent": { + "properties": { + "at": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "from": { + "type": [ + "string", + "null" + ] + }, + "heading": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "$ref": "#/components/schemas/TicketEventKind" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "references": { + "items": { + "$ref": "#/components/schemas/TicketReference" + }, + "type": "array" + }, + "state_field": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "to": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "body", + "references", + "attributes" + ], + "type": "object" + }, + "TicketEventDetail": { + "additionalProperties": false, + "properties": { + "at": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": [ + "string", + "null" + ] + }, + "event_ref": { + "type": "string" + }, + "from": { + "type": [ + "string", + "null" + ] + }, + "heading": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "references": { + "items": { + "type": "string" + }, + "type": "array" + }, + "sequence": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "state_field": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "to": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "sequence", + "event_ref", + "kind", + "attributes", + "references" + ], + "type": "object" + }, + "TicketEventKind": { + "oneOf": [ + { + "enum": [ + "create", + "comment", + "plan", + "decision", + "implementation_report", + "state_changed", + "intake_summary", + "status_changed", + "close" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "type": "object" + } + ] + }, + "TicketEvidenceEvent": { + "additionalProperties": false, + "properties": { + "at": { + "type": [ + "string", + "null" + ] + }, + "author": { + "type": [ + "string", + "null" + ] + }, + "event_ref": { + "type": "string" + }, + "excerpt": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "sequence": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "event_ref", + "sequence", + "kind", + "excerpt" + ], + "type": "object" + }, + "TicketEvidenceSummary": { + "additionalProperties": false, + "properties": { + "approved_current_subject": { + "type": "boolean" + }, + "complete_for_integration": { + "type": "boolean" + }, + "has_commit": { + "type": "boolean" + }, + "has_current_subject_ref": { + "type": "boolean" + }, + "has_merge_request": { + "type": "boolean" + }, + "has_review_request": { + "type": "boolean" + }, + "missing": { + "items": { + "type": "string" + }, + "type": "array" + }, + "review_after_rescope": { + "type": "boolean" + }, + "review_status": { + "type": [ + "string", + "null" + ] + }, + "unresolved_request_changes": { + "type": "boolean" + } + }, + "required": [ + "has_merge_request", + "has_current_subject_ref", + "has_review_request", + "has_commit", + "approved_current_subject", + "review_after_rescope", + "unresolved_request_changes", + "complete_for_integration", + "missing" + ], + "type": "object" + }, + "TicketIdOrSlug": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + } + }, + "required": [ + "Id" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "Slug": { + "type": "string" + } + }, + "required": [ + "Slug" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "Query": { + "type": "string" + } + }, + "required": [ + "Query" + ], + "type": "object" + } + ] + }, + "TicketIntakeSummary": { + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "references": { + "items": { + "$ref": "#/components/schemas/TicketReference" + }, + "type": "array" + } + }, + "required": [ + "body", + "references" + ], + "type": "object" + }, + "TicketItemEdit": { + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "anyOf": [ + { + "$ref": "#/components/schemas/MarkdownText" + }, + { + "type": "null" + } + ], + "description": "Whole-body replacement for legacy authoring surfaces. Prefer `body_replacement`\nfor small edits that should be validated against the current body." + }, + "body_replacement": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketBodyReplacement" + }, + { + "type": "null" + } + ], + "default": null + }, + "target": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketTargetEdit" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TicketListHttpQuery": { + "additionalProperties": false, + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "states": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TicketListItemSummary": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "priority": { + "type": "string" + }, + "queued_at": { + "type": [ + "string", + "null" + ] + }, + "queued_by": { + "type": [ + "string", + "null" + ] + }, + "record_source": { + "type": "string" + }, + "resource_key": { + "type": "string" + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "workspace_action_priority": { + "type": "string" + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "priority", + "workspace_action_priority", + "record_source" + ], + "type": "object" + }, + "TicketListResponse": { + "additionalProperties": false, + "properties": { + "invalid_records": { + "items": { + "$ref": "#/components/schemas/InvalidProjectRecord" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/TicketListItemSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": "integer" + }, + "page": { + "$ref": "#/components/schemas/QueryPage" + }, + "record_authority": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "page", + "invalid_records", + "record_authority" + ], + "type": "object" + }, + "TicketMarkReadyRequest": { + "additionalProperties": false, + "properties": { + "intake_summary": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketIntakeSummary" + }, + { + "type": "null" + } + ], + "default": null + }, + "operation_key": { + "type": "string" + }, + "reason": { + "default": null, + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "operation_key" + ], + "type": "object" + }, + "TicketMergeRequestSummary": { + "additionalProperties": false, + "properties": { + "current_subject_ref": { + "type": [ + "string", + "null" + ] + }, + "merge_request_id": { + "type": "string" + }, + "repository_key": { + "type": "string" + }, + "review_excerpt": { + "type": [ + "string", + "null" + ] + }, + "review_requested_at": { + "type": [ + "string", + "null" + ] + }, + "review_status": { + "type": "string" + }, + "review_subject_ref": { + "type": [ + "string", + "null" + ] + }, + "review_submitted_at": { + "type": [ + "string", + "null" + ] + }, + "selector_from": { + "type": [ + "string", + "null" + ] + }, + "selector_to": { + "type": "string" + }, + "state": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "merge_request_id", + "repository_key", + "state", + "review_status", + "selector_to", + "updated_at" + ], + "type": "object" + }, + "TicketMeta": { + "properties": { + "assignee": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "priority": { + "type": "string" + }, + "queued_at": { + "type": [ + "string", + "null" + ] + }, + "queued_by": { + "type": [ + "string", + "null" + ] + }, + "raw": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "readiness": { + "type": [ + "string", + "null" + ] + }, + "ref_selector": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": [ + "string", + "null" + ] + }, + "resource_key": { + "type": [ + "string", + "null" + ] + }, + "risk_flags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "slug": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/ExtensibleTicketStatus" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "workflow_state": { + "$ref": "#/components/schemas/TicketWorkflowState" + }, + "workflow_state_explicit": { + "type": "boolean" + } + }, + "required": [ + "id", + "slug", + "title", + "status", + "kind", + "priority", + "labels", + "risk_flags", + "workflow_state", + "workflow_state_explicit", + "raw" + ], + "type": "object" + }, + "TicketOrchestrationPlanSearchRequest": { + "additionalProperties": false, + "properties": { + "kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrchestrationPlanKind" + }, + { + "type": "null" + } + ] + }, + "ticket": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketIdOrSlug" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "TicketQueryItem": { + "additionalProperties": false, + "properties": { + "blocker_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "current_coder": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketAssignmentSummary" + }, + { + "type": "null" + } + ] + }, + "evidence": { + "$ref": "#/components/schemas/TicketEvidenceSummary" + }, + "id": { + "type": "string" + }, + "item_revision": { + "type": "string" + }, + "linked_objective_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_objective_keys": { + "items": { + "type": "string" + }, + "type": "array" + }, + "matched_fields": { + "items": { + "type": "string" + }, + "type": "array" + }, + "matching_event": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketEvidenceEvent" + }, + { + "type": "null" + } + ] + }, + "merge_request": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketMergeRequestSummary" + }, + { + "type": "null" + } + ] + }, + "priority": { + "type": "string" + }, + "readiness": { + "type": [ + "string", + "null" + ] + }, + "relation_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "resource_key": { + "type": "string" + }, + "snippet": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": "string" + }, + "title": { + "type": "string" + }, + "unresolved_blocker_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "unresolved_review_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "workspace_action_priority": { + "type": "string" + } + }, + "required": [ + "id", + "resource_key", + "title", + "state", + "priority", + "item_revision", + "workspace_action_priority", + "matched_fields", + "linked_objective_ids", + "linked_objective_keys", + "relation_count", + "blocker_count", + "unresolved_blocker_count", + "unresolved_review_count", + "evidence" + ], + "type": "object" + }, + "TicketQueryRequest": { + "additionalProperties": false, + "properties": { + "attention": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "cursor": { + "type": [ + "string", + "null" + ] + }, + "event_kinds": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "evidence": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "linked_objective_id": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "related_ticket_id": { + "type": [ + "string", + "null" + ] + }, + "relation_kind": { + "type": [ + "string", + "null" + ] + }, + "review_status": { + "type": [ + "string", + "null" + ] + }, + "sort": { + "type": [ + "string", + "null" + ] + }, + "states": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_after": { + "type": [ + "string", + "null" + ] + }, + "updated_before": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TicketQueryResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/TicketQueryItem" + }, + "type": "array" + }, + "page": { + "$ref": "#/components/schemas/QueryPage" + }, + "record_authority": { + "type": "string" + } + }, + "required": [ + "items", + "page", + "record_authority" + ], + "type": "object" + }, + "TicketQueueGuard": { + "properties": { + "blocked_reason": { + "type": [ + "string", + "null" + ] + }, + "can_queue_for_orchestrator": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "can_queue_for_orchestrator" + ], + "type": "object" + }, + "TicketQueueOutcome": { + "properties": { + "queued_tickets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requested_ticket": { + "type": "string" + } + }, + "required": [ + "requested_ticket", + "queued_tickets" + ], + "type": "object" + }, + "TicketRef": { + "properties": { + "id": { + "type": "string" + }, + "resource_key": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TicketStatus" + } + }, + "required": [ + "id", + "slug", + "status" + ], + "type": "object" + }, + "TicketReference": { + "properties": { + "kind": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + "TicketRelation": { + "additionalProperties": false, + "properties": { + "at": { + "type": "string" + }, + "author": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/TicketRelationKind" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": "string" + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "ticket_id", + "kind", + "target", + "author", + "at" + ], + "type": "object" + }, + "TicketRelationBlocker": { + "properties": { + "blocking_state": { + "$ref": "#/components/schemas/TicketWorkflowState" + }, + "blocking_ticket": { + "type": "string" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "reason_kind": { + "type": "string" + }, + "relation_kind": { + "$ref": "#/components/schemas/TicketRelationKind" + } + }, + "required": [ + "blocking_ticket", + "reason_kind", + "relation_kind", + "blocking_state" + ], + "type": "object" + }, + "TicketRelationKind": { + "enum": [ + "depends_on", + "blocks", + "related", + "supersedes", + "duplicate_of" + ], + "type": "string" + }, + "TicketRelationNotice": { + "properties": { + "kind": { + "$ref": "#/components/schemas/TicketRelationKind" + }, + "message": { + "type": "string" + }, + "related_ticket": { + "type": "string" + } + }, + "required": [ + "related_ticket", + "kind", + "message" + ], + "type": "object" + }, + "TicketRelationRemoveRequest": { + "additionalProperties": false, + "properties": { + "kind": { + "$ref": "#/components/schemas/TicketRelationKind" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + "TicketRelationSearchRequest": { + "additionalProperties": false, + "properties": { + "kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketRelationKind" + }, + { + "type": "null" + } + ] + }, + "ticket": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketIdOrSlug" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "TicketRelationView": { + "properties": { + "blockers": { + "items": { + "$ref": "#/components/schemas/TicketRelationBlocker" + }, + "type": "array" + }, + "incoming": { + "items": { + "$ref": "#/components/schemas/DerivedTicketRelation" + }, + "type": "array" + }, + "notices": { + "items": { + "$ref": "#/components/schemas/TicketRelationNotice" + }, + "type": "array" + }, + "outgoing": { + "items": { + "$ref": "#/components/schemas/TicketRelation" + }, + "type": "array" + } + }, + "required": [ + "outgoing", + "incoming", + "blockers", + "notices" + ], + "type": "object" + }, + "TicketRoleAssignmentMutationResponse": { + "additionalProperties": false, + "properties": { + "assignment": { + "anyOf": [ + { + "$ref": "#/components/schemas/TicketRoleAssignmentRecord" + }, + { + "type": "null" + } + ] + }, + "ticket_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "ticket_id" + ], + "type": "object" + }, + "TicketRoleAssignmentRecord": { + "additionalProperties": false, + "properties": { + "assigned_at": { + "type": "string" + }, + "assigned_by": { + "type": "string" + }, + "assignment_id": { + "type": "string" + }, + "principal": { + "$ref": "#/components/schemas/TicketAssignmentPrincipal" + }, + "role": { + "$ref": "#/components/schemas/TicketAssignmentRole" + }, + "ticket_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "ticket_id", + "assignment_id", + "role", + "principal", + "assigned_by", + "assigned_at" + ], + "type": "object" + }, + "TicketRoleAssignmentSummary": { + "additionalProperties": false, + "properties": { + "assigned_at": { + "type": "string" + }, + "assigned_by": { + "type": "string" + }, + "assignment_id": { + "type": "string" + }, + "principal": { + "$ref": "#/components/schemas/TicketAssignmentPrincipal" + }, + "role": { + "type": "string" + } + }, + "required": [ + "assignment_id", + "role", + "principal", + "assigned_by", + "assigned_at" + ], + "type": "object" + }, + "TicketRoleAssignmentsResponse": { + "additionalProperties": false, + "properties": { + "assignments": { + "items": { + "$ref": "#/components/schemas/TicketRoleAssignmentRecord" + }, + "type": "array" + }, + "ticket_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "ticket_id", + "assignments" + ], + "type": "object" + }, + "TicketShowRequest": { + "additionalProperties": false, + "properties": { + "event_cursor": { + "type": [ + "string", + "null" + ] + }, + "event_limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "TicketStateChange": { + "properties": { + "author": { + "type": [ + "string", + "null" + ] + }, + "body": { + "$ref": "#/components/schemas/MarkdownText" + }, + "from": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "references": { + "items": { + "$ref": "#/components/schemas/TicketReference" + }, + "type": "array" + }, + "to": { + "type": "string" + } + }, + "required": [ + "from", + "to", + "reason", + "body", + "references" + ], + "type": "object" + }, + "TicketStatus": { + "enum": [ + "Open", + "Closed" + ], + "type": "string" + }, + "TicketSummary": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "priority": { + "type": "string" + }, + "queued_at": { + "type": [ + "string", + "null" + ] + }, + "queued_by": { + "type": [ + "string", + "null" + ] + }, + "readiness": { + "type": [ + "string", + "null" + ] + }, + "resource_key": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/ExtensibleTicketStatus" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "workflow_state": { + "$ref": "#/components/schemas/TicketWorkflowState" + }, + "workflow_state_explicit": { + "type": "boolean" + } + }, + "required": [ + "id", + "slug", + "title", + "status", + "kind", + "priority", + "labels", + "workflow_state", + "workflow_state_explicit" + ], + "type": "object" + }, + "TicketSummarySearchQuery": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint", + "maximum": 1000, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "state": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TicketTargetEdit": { + "oneOf": [ + { + "properties": { + "action": { + "const": "set", + "type": "string" + }, + "ref_selector": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": "string" + } + }, + "required": [ + "action", + "repository_key" + ], + "type": "object" + }, + { + "properties": { + "action": { + "const": "clear", + "type": "string" + } + }, + "required": [ + "action" + ], + "type": "object" + } + ] + }, + "TicketWorkflowState": { + "enum": [ + "planning", + "ready", + "queued", + "in_progress", + "done", + "closed" + ], + "type": "string" + }, + "TicketWorkspaceNextAction": { + "enum": [ + "clarify", + "queue_for_orchestrator", + "close", + "wait_for_orchestrator" + ], + "type": "string" + }, "ToolchainContract": { "additionalProperties": false, "properties": { @@ -4809,7 +9322,7 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:386c56c830b49766e9f523f08c5bd4e24de490ba18c99c3d04063021d6b33352" + "x-yoi-source-digest": "sha256:0026ff3d0f1d267e4de3f154a14f4ef3f7554b603a376820f0775e5deb16a083" }, "openapi": "3.1.0", "paths": { @@ -5227,6 +9740,114 @@ } } }, + "/api/objectives": { + "get": { + "operationId": "objective_list_alias", + "parameters": [ + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/ObjectiveListQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/objectives/{id}": { + "get": { + "operationId": "objective_get_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/repositories": { "get": { "operationId": "repository_list_alias", @@ -5389,6 +10010,114 @@ ] } }, + "/api/tickets": { + "get": { + "operationId": "ticket_list_alias", + "parameters": [ + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/TicketListHttpQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/tickets/{id}": { + "get": { + "operationId": "ticket_get_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/w/{workspace_id}/config/projections/prompts": { "get": { "operationId": "workspace_prompt_projection", @@ -6210,6 +10939,98 @@ } } }, + "/api/w/{workspace_id}/internal/reviewer-child-sessions": { + "post": { + "operationId": "merge_request_reviewer_child_register", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterReviewerChildSessionRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/w/{workspace_id}/memory": { "get": { "operationId": "memory_document", @@ -6602,6 +11423,1163 @@ ] } }, + "/api/w/{workspace_id}/merge-requests": { + "get": { + "operationId": "merge_request_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/MergeRequestListQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MergeRequestListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/merge-requests/{merge_request_id}": { + "get": { + "operationId": "merge_request_show", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "merge_request_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/MergeRequestThreadQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MergeRequestDetailResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/objectives": { + "get": { + "operationId": "objective_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/ObjectiveListQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "objective_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/objectives/query": { + "post": { + "operationId": "objective_query", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveQueryRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveQueryResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/objectives/{objective_id}": { + "get": { + "operationId": "objective_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "patch": { + "operationId": "objective_edit", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveEditRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/objectives/{objective_id}/show": { + "post": { + "operationId": "objective_show", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveShowRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/objectives/{objective_id}/state": { + "post": { + "operationId": "objective_state_set", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveStateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links": { + "post": { + "operationId": "objective_ticket_link", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveLinkTicketRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links/{ticket_id}": { + "delete": { + "operationId": "objective_ticket_unlink", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "objective_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "ticket_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/repositories": { "get": { "operationId": "repository_list", @@ -9168,6 +15146,4486 @@ } } }, + "/api/w/{workspace_id}/tickets": { + "get": { + "operationId": "ticket_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/TicketListHttpQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "ticket_create_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewTicket" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRef" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/default-intake-ready-body": { + "post": { + "operationId": "ticket_default_intake_ready_body", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DefaultIntakeReadyBodyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/string" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/doctor": { + "get": { + "operationId": "ticket_doctor", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDoctorReport" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/orchestration-plans/search": { + "post": { + "operationId": "ticket_orchestration_plan_query", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketOrchestrationPlanSearchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_OrchestrationPlanRecord" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/query": { + "post": { + "operationId": "ticket_query", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketQueryRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketQueryResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/relations/search": { + "post": { + "operationId": "ticket_relation_query", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRelationSearchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_TicketRelation" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/search": { + "get": { + "operationId": "ticket_summary_search", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/TicketSummarySearchQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_TicketSummary" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}": { + "get": { + "operationId": "ticket_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "patch": { + "operationId": "ticket_edit", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserEditTicketRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/assignments": { + "get": { + "operationId": "ticket_assignment_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRoleAssignmentsResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/assignments/{role}": { + "delete": { + "operationId": "ticket_assignment_clear", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "role", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/ClearTicketRoleAssignmentQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRoleAssignmentMutationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "put": { + "operationId": "ticket_assignment_set", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "role", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetTicketRoleAssignmentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRoleAssignmentMutationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/close": { + "post": { + "operationId": "ticket_close", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserCloseTicketRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/dependency-check": { + "get": { + "operationId": "ticket_dependency_check", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDependencyCheck" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/events": { + "post": { + "operationId": "ticket_event_append", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserAppendTicketEventRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/implementation-cancellations": { + "post": { + "operationId": "ticket_implementation_cancel", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CancelTicketImplementationRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/intake-summaries": { + "post": { + "operationId": "ticket_intake_summary_add", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketIntakeSummary" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/item": { + "patch": { + "operationId": "ticket_record_item_edit", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketItemEdit" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ticket" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request": { + "post": { + "operationId": "merge_request_open", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenMergeRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicMergeRequest" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/complete": { + "post": { + "operationId": "merge_request_complete", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompleteMergeRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MergeEvent" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/readiness": { + "get": { + "operationId": "merge_request_readiness", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MergeRequestReadinessResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/repair-source": { + "post": { + "operationId": "merge_request_selector_repair", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairMergeRequestSelectorRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicMergeRequest" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/review-capabilities": { + "post": { + "operationId": "merge_request_review_capability_register", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterMergeRequestReviewCapabilityRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/reviews": { + "post": { + "operationId": "merge_request_review_submit", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitMergeRequestReviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewEvent" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/reviews/revoke": { + "post": { + "operationId": "merge_request_review_revoke", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevokeMergeRequestReviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewRevokedEvent" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/merge-request/thread": { + "get": { + "operationId": "merge_request_thread", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/MergeRequestThreadQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Array_of_MergeRequestThreadEvent" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/orchestration-plans": { + "post": { + "operationId": "ticket_orchestration_plan_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewOrchestrationPlanRecord" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrchestrationPlanRecord" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/queue": { + "post": { + "operationId": "ticket_queue", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserQueueTicketRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketQueueOutcome" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/ready": { + "post": { + "operationId": "ticket_ready", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketMarkReadyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/record": { + "get": { + "operationId": "ticket_record_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ticket" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/relation-view": { + "get": { + "operationId": "ticket_relation_view", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRelationView" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/relations": { + "delete": { + "operationId": "ticket_relation_remove", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRelationRemoveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRelation" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + }, + "post": { + "operationId": "ticket_relation_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewTicketRelation" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketRelation" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/show": { + "post": { + "operationId": "ticket_show", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketShowRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/state": { + "post": { + "operationId": "ticket_state_transition", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserTransitionTicketStateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/tickets/{id}/state-changes": { + "post": { + "operationId": "ticket_state_change_add", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketStateChange" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/state-fields/{field}": { + "post": { + "operationId": "ticket_state_field_set", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "field", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketStateChange" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/thread-events": { + "post": { + "operationId": "ticket_thread_event_add", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewTicketEvent" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/workflow-state": { + "post": { + "operationId": "ticket_workflow_state_set", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketStateChange" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/workflow/close": { + "post": { + "operationId": "ticket_close_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarkdownText" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/workflow/mark-ready": { + "post": { + "operationId": "ticket_mark_ready_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketMarkReadyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ticket" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/w/{workspace_id}/tickets/{id}/workflow/queue": { + "post": { + "operationId": "ticket_queue_record", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketQueueOutcome" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/w/{workspace_id}/workspace": { "get": { "operationId": "workspace_scoped", diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index 05261c9bb..c450bf1e9 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:386c56c830b49766e9f523f08c5bd4e24de490ba18c99c3d04063021d6b33352) +// Input contract: 0.1.0 (sha256:0026ff3d0f1d267e4de3f154a14f4ef3f7554b603a376820f0775e5deb16a083) // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Output: web/workspace/src/lib/generated/repository-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check From 7a975765acfff5e9f1c14d2e27dd8e43b23e2c2f Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 06:51:05 +0900 Subject: [PATCH 04/16] feat: migrate repository and workdir domains to ServerApi --- Cargo.lock | 2 + crates/fs-operation/Cargo.toml | 1 + crates/fs-operation/src/operation.rs | 61 +- crates/server-api/Cargo.toml | 1 - .../examples/generate_workdir_api_types.rs | 61 +- crates/server-api/src/lib.rs | 557 +- .../src/repository_openapi_typescript.rs | 366 +- crates/workdir/Cargo.toml | 1 + crates/workdir/src/http.rs | 17 +- crates/workdir/src/lib.rs | 9 +- crates/workdir/src/operation.rs | 31 +- crates/workdir/src/scope.rs | 12 +- crates/workdir/src/workspace.rs | 27 +- crates/workspace-server/src/hosts.rs | 17 + crates/workspace-server/src/server.rs | 697 ++- openapi/server-api.json | 4816 ++++++++++++++++- .../src/lib/generated/repository-api.ts | 46 +- .../src/lib/generated/workdir-api.ts | 124 +- .../src/lib/workspace/api/workspace-model.ts | 93 +- .../src/lib/workspace/sidebar/types.ts | 13 - .../src/routes/w/[workspaceId]/+page.ts | 18 +- .../repositories/[repositoryKey]/+page.svelte | 2 +- 22 files changed, 6711 insertions(+), 261 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ad43461a..cc29a095e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1447,6 +1447,7 @@ dependencies = [ "grep-searcher", "ignore", "libc", + "schemars", "serde", "serde_json", "sha2 0.10.9", @@ -6104,6 +6105,7 @@ dependencies = [ "fs-operation", "manifest", "reqwest", + "schemars", "serde", "serde_json", "sha2 0.11.0", diff --git a/crates/fs-operation/Cargo.toml b/crates/fs-operation/Cargo.toml index f61b8be76..6d360b536 100644 --- a/crates/fs-operation/Cargo.toml +++ b/crates/fs-operation/Cargo.toml @@ -11,6 +11,7 @@ grep-regex = "0.1.14" grep-searcher = "0.1.16" ignore = "0.4.25" libc = "0.2" +schemars.workspace = true serde = { workspace = true, features = ["derive"] } sha2 = "0.10.9" tempfile.workspace = true diff --git a/crates/fs-operation/src/operation.rs b/crates/fs-operation/src/operation.rs index 4d74184b9..f5765b6f6 100644 --- a/crates/fs-operation/src/operation.rs +++ b/crates/fs-operation/src/operation.rs @@ -1,6 +1,7 @@ use std::fmt; use std::path::{Component, Path, PathBuf}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::FsError; @@ -11,9 +12,11 @@ use crate::FsError; /// while computing line and content metadata. `max_response_bytes` limits the /// bytes retained for the response independently of the caller's requested /// `ReadRequest::max_bytes`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)] pub struct BoundedReadLimits { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub max_source_bytes: u64, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub max_response_bytes: usize, } @@ -72,7 +75,7 @@ impl<'de> Deserialize<'de> for BoundedReadLimits { /// Scope-checked filesystem path. Relative paths resolve below the bound /// Workdir root; absolute paths require an explicit matching scope rule. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, JsonSchema)] #[serde(transparent)] pub struct FsPath(String); @@ -153,21 +156,22 @@ impl fmt::Display for FsPath { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct StatRequest { pub path: FsPath, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct StatResult { pub path: FsPath, pub kind: EntryKind, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub size: u64, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum EntryKind { File, @@ -178,27 +182,32 @@ pub enum EntryKind { pub type ContentHash = [u8; 32]; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ReadRequest { pub path: FsPath, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub offset: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub max_bytes: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ReadResult { pub path: FsPath, pub bytes: Vec, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub start_line: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub total_lines: usize, pub content_hash: ContentHash, pub truncated: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WriteRequest { pub path: FsPath, @@ -206,13 +215,14 @@ pub struct WriteRequest { pub expected_hash: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct WriteResult { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub bytes_written: usize, pub created: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct EditRequest { pub path: FsPath, @@ -222,54 +232,61 @@ pub struct EditRequest { pub expected_hash: ContentHash, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct EditResult { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub replacements: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub bytes_written: usize, pub content_hash: ContentHash, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ListRequest { pub path: FsPath, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ListEntry { pub path: FsPath, pub kind: EntryKind, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub size: u64, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ListResult { pub entries: Vec, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub total_entries: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub total_bytes: u64, pub truncated: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct GlobRequest { pub pattern: String, pub path: FsPath, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct GlobResult { pub paths: Vec, pub truncated: bool, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum GrepOutputMode { Content, @@ -283,7 +300,7 @@ impl Default for GrepOutputMode { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct GrepRequest { pub pattern: String, @@ -291,21 +308,27 @@ pub struct GrepRequest { pub glob: Option, pub file_type: Option, pub case_insensitive: bool, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub before_context: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub after_context: usize, pub multiline: bool, pub output_mode: GrepOutputMode, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub offset: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct GrepResult { /// Provider-rendered bounded grep report. Keeping rendering here avoids /// transferring candidate files across a remote provider boundary. pub output: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub match_count: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub matched_files: usize, pub truncated: bool, } diff --git a/crates/server-api/Cargo.toml b/crates/server-api/Cargo.toml index a74b4c020..47452a8f0 100644 --- a/crates/server-api/Cargo.toml +++ b/crates/server-api/Cargo.toml @@ -33,7 +33,6 @@ required-features = ["typescript"] [[example]] name = "generate_workdir_api_types" -required-features = ["typescript"] [[example]] name = "generate_worker_launch_api_types" diff --git a/crates/server-api/examples/generate_workdir_api_types.rs b/crates/server-api/examples/generate_workdir_api_types.rs index 13ed5f08b..ff8dff334 100644 --- a/crates/server-api/examples/generate_workdir_api_types.rs +++ b/crates/server-api/examples/generate_workdir_api_types.rs @@ -1,6 +1,59 @@ +use std::fs; +use std::path::Path; + +use server_api::repository_openapi_typescript::{ + INPUT_PATH, WORKDIR_OUTPUT_PATH, generate_workdir_typescript, +}; + fn main() { - print!( - "{}", - server_api::normalized_typescript(server_api::workdir_api_typescript()) - ); + if let Err(error) = run() { + eprintln!("{error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let mut check = false; + for argument in std::env::args().skip(1) { + match argument.as_str() { + "--check" => check = true, + "--help" | "-h" => { + println!( + "Generate the bounded Workdir API TypeScript artifact.\n\n\ + Usage: cargo run -q -p server-api --example generate_workdir_api_types -- [--check]" + ); + return Ok(()); + } + other => return Err(format!("unknown argument `{other}`")), + } + } + + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let input_path = root.join(INPUT_PATH); + let output_path = root.join(WORKDIR_OUTPUT_PATH); + let input = fs::read_to_string(&input_path) + .map_err(|error| format!("failed to read {}: {error}", input_path.display()))?; + let generated = generate_workdir_typescript(&input) + .map_err(|error| format!("failed to generate Workdir API types: {error}"))?; + + if check { + let checked_in = fs::read_to_string(&output_path) + .map_err(|error| format!("failed to read {}: {error}", output_path.display()))?; + if checked_in != generated { + return Err(format!( + "{} is stale; regenerate it without --check", + output_path.display() + )); + } + println!("{} is current", output_path.display()); + return Ok(()); + } + + let temporary_path = output_path.with_extension("ts.tmp"); + fs::write(&temporary_path, generated) + .map_err(|error| format!("failed to write {}: {error}", temporary_path.display()))?; + fs::rename(&temporary_path, &output_path) + .map_err(|error| format!("failed to replace {}: {error}", output_path.display()))?; + println!("generated {}", output_path.display()); + Ok(()) } diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index ba6632d7d..6943e3a87 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -77,6 +77,64 @@ impl api_macros::HttpRequestError for RepositoryApiError { } } +/// Error body for current-Worker Workdir operations. +/// +/// Domain/service failures preserve the existing Repository-style API envelope, +/// while provider failures preserve the established typed Workdir transport +/// envelope and HTTP status classification. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WorkdirOperationApiError { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code: Option, + pub message: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub diagnostics: Option>, + #[serde(skip, default = "default_repository_error_status")] + status: u16, +} + +impl WorkdirOperationApiError { + pub fn api(error: RepositoryApiError) -> Self { + Self { + error: Some(error.error), + code: None, + message: error.message, + diagnostics: Some(error.diagnostics), + status: error.status, + } + } + + pub fn provider(error: workdir::http::WorkdirTransportError) -> Self { + let status = error.code.http_status(); + Self { + error: None, + code: Some(error.code), + message: error.message, + diagnostics: None, + status, + } + } +} + +impl api_macros::HttpError for WorkdirOperationApiError { + fn status_code(&self) -> u16 { + self.code.map_or(self.status, |code| code.http_status()) + } +} + +impl api_macros::HttpRequestError for WorkdirOperationApiError { + fn from_request_rejection(status: u16, message: String) -> Self { + Self::api( + ::from_request_rejection( + status, message, + ), + ) + } +} + macro_rules! impl_openapi_schema { ($($ty:ty),+ $(,)?) => { $(impl api_macros::openapi::OpenApiSchema for $ty {})+ @@ -109,8 +167,29 @@ impl_openapi_schema!( CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse, RepositoryApiError, + WorkdirOperationApiError, RepositoryListResponse, RepositoryDetailResponse, + RepositorySshConnectionProbeRequest, + RepositorySshConnectionProbeResponse, + ConfirmRepositorySshHostTrustRequest, + RepositoryLogQuery, + RepositoryLogResponse, + HostListResponse, + HostWorkerListResponse, + HostSummary, + WorkingDirectoryRemovalRequest, + WorkingDirectoryRemovalResponse, + WorkingDirectoryCreateRequest, + WorkingDirectoryListResponse, + WorkingDirectoryDetailResponse, + WorkingDirectoryCreateResponse, + CurrentWorkerWorkdirAttachRequest, + CurrentWorkerWorkdirAttachmentResponse, + CurrentWorkerWorkdirOperationRequest, + CurrentWorkerWorkdirOperationResponse, + ExternalWorkdirGrantCreateRequest, + ExternalWorkdirGrantResponse, WorkspaceResponse, WorkspaceMetadataSettingsResponse, UpdateWorkspaceMetadataRequest, @@ -980,6 +1059,324 @@ pub trait ServerApi { #[body] request: CreateWorkspaceRepositoryRequest, ) -> Result; + #[post( + "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_connection_probe( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] repository_key: String, + #[body] request: RepositorySshConnectionProbeRequest, + ) -> Result; + + #[put( + "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn repository_ssh_host_trust_confirm( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] repository_key: String, + #[body] request: ConfirmRepositorySshHostTrustRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/repositories/{repository_key}/log", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_log( + &self, + #[path] workspace_id: String, + #[path] repository_key: String, + #[query] query: RepositoryLogQuery, + ) -> Result; + + #[get( + "/api/repositories/{repository_key}/log", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn repository_log_alias( + &self, + #[path] repository_key: String, + #[query] query: RepositoryLogQuery, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/hosts", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn host_list( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[get( + "/api/hosts", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn host_list_alias(&self) -> Result; + + #[get( + "/api/w/{workspace_id}/hosts/{host_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn host_worker_list( + &self, + #[path] workspace_id: String, + #[path] host_id: String, + ) -> Result; + + #[get( + "/api/hosts/{host_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn host_worker_list_alias( + &self, + #[path] host_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_workdir_list( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", + status = 201, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_workdir_create( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: WorkingDirectoryCreateRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_workdir_detail( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] working_directory_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_workdir_cleanup( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] working_directory_id: String, + #[body] request: WorkingDirectoryRemovalRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/workers/self/workdir-attachments", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true + )] + async fn current_worker_workdir_attach( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: CurrentWorkerWorkdirAttachRequest, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true + )] + async fn current_worker_workdir_detach( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] alias: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/workers/self/workdir-session/operations", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true + )] + async fn current_worker_workdir_operation( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[body] request: CurrentWorkerWorkdirOperationRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/external-workdir-grants", + status = 201, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn external_workdir_grant_create( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[body] request: ExternalWorkdirGrantCreateRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn external_workdir_grant_get( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] grant_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn external_workdir_grant_revoke( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] grant_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/working-directories", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_workdir_list( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/working-directories", + status = 201, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_workdir_create( + &self, + #[path] workspace_id: String, + #[body] request: WorkingDirectoryCreateRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/working-directories/{working_directory_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_workdir_detail( + &self, + #[path] workspace_id: String, + #[path] working_directory_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/working-directories/{working_directory_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn workspace_workdir_cleanup( + &self, + #[extension] context: ServerRequestContext, + #[path] workspace_id: String, + #[path] working_directory_id: String, + #[body] request: WorkingDirectoryRemovalRequest, + ) -> Result; + #[get("/api/tickets", status = 200, error_status = 400, additional_error_statuses = [500])] async fn ticket_list_alias( &self, @@ -1488,9 +1885,16 @@ pub fn canonical_openapi_document() /// /// Generated clients and OpenAPI omit extension parameters; operation implementations use this /// value instead of re-parsing transport headers inside domain handlers. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ServerWorkerSource { + pub runtime_id: String, + pub worker_id: String, +} + #[derive(Clone)] pub struct ServerRequestContext { pub actor: Option, + pub worker_source: Option, pub origin: Option, /// Transport headers retained for Server-side authentication adapters. /// @@ -2991,7 +3395,7 @@ pub struct RepositorySummary { pub diagnostics: Option>, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct GitCommitSummary { @@ -3024,14 +3428,14 @@ pub struct RepositoryDetailResponse { pub source: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshConnectionProbeRequest { pub runtime_id: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshHostKeyCandidate { @@ -3040,7 +3444,7 @@ pub struct RepositorySshHostKeyCandidate { pub fingerprint: String, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RepositorySshConnectionTrustState { @@ -3049,7 +3453,7 @@ pub enum RepositorySshConnectionTrustState { Changed, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositorySshConnectionProbeResponse { @@ -3061,11 +3465,12 @@ pub struct RepositorySshConnectionProbeResponse { pub trust_state: RepositorySshConnectionTrustState, pub host_trust_id: String, #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_host_trust_revision: Option, pub candidates: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct ConfirmRepositorySshHostTrustRequest { @@ -3073,10 +3478,19 @@ pub struct ConfirmRepositorySshHostTrustRequest { pub runtime_id: String, pub host_key: String, #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_host_trust_revision: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RepositoryLogQuery { + #[serde(default)] + #[schemars(range(min = 1, max = 200))] + pub limit: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RepositoryLogResponse { @@ -3085,6 +3499,7 @@ pub struct RepositoryLogResponse { #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional = nullable))] pub default_selector: Option, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, pub items: Vec, pub diagnostics: Vec, @@ -3115,7 +3530,7 @@ pub struct Diagnostic { /// /// The value identifies stable materialization provenance without exposing a /// provider path, Runtime handle, or session identity. -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkingDirectoryMaterializerKind { @@ -3124,7 +3539,7 @@ pub enum WorkingDirectoryMaterializerKind { ClientHostedExternal, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkingDirectoryStatusKind { @@ -3153,7 +3568,7 @@ impl std::fmt::Display for WorkingDirectoryStatusKind { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryCleanupTarget { @@ -3162,14 +3577,14 @@ pub struct WorkingDirectoryCleanupTarget { pub repository_key: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryRemovalRequest { pub reason: String, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkingDirectoryRemovalDisposition { @@ -3178,7 +3593,7 @@ pub enum WorkingDirectoryRemovalDisposition { AttentionRequired, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3191,7 +3606,7 @@ pub struct WorkingDirectoryRemovalResponse { } /// Durable Workspace occupancy projection for one Workdir. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryOccupancy { @@ -3204,7 +3619,7 @@ pub struct WorkingDirectoryOccupancy { /// Runtime-internal Workdir cleanup authority. This transport intentionally /// retains the Backend-generated Repository id and is never a Workspace public /// projection. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3217,7 +3632,7 @@ pub struct RuntimeWorkingDirectoryCleanupTarget { /// Runtime-internal Workdir inventory transport. Workspace REST and model-facing /// surfaces must project this through [`WorkingDirectorySummary`] so the UUID is /// replaced with `repository_key`. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3241,6 +3656,7 @@ pub struct RuntimeWorkingDirectorySummary { pub current_tree: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub observed_at_epoch_seconds: Option, pub materializer_kind: WorkingDirectoryMaterializerKind, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -3253,7 +3669,7 @@ pub struct RuntimeWorkingDirectorySummary { } /// Public Workdir source identity. Provider routing details and host paths are intentionally absent. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] pub enum WorkingDirectorySource { @@ -3262,7 +3678,7 @@ pub enum WorkingDirectorySource { } /// Public, provider-neutral Workdir inventory projection. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3286,6 +3702,7 @@ pub struct WorkingDirectorySummary { pub current_tree: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional, type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub observed_at_epoch_seconds: Option, pub materializer_kind: WorkingDirectoryMaterializerKind, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -3414,17 +3831,18 @@ impl From for WorkingDirectorySumma } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct ExternalWorkdirGrantCreateRequest { pub provider_instance_id: String, pub display_name: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub ttl_seconds: u64, pub read_only: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct ExternalWorkdirGrantResponse { @@ -3435,6 +3853,7 @@ pub struct ExternalWorkdirGrantResponse { pub display_name: String, pub permissions: String, pub expires_at: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub generation: u64, pub status: String, } @@ -3444,7 +3863,7 @@ pub struct ExternalWorkdirGrantResponse { /// `runtime_id = None` requests Workspace default Runtime resolution and /// `operation_id = Some(_)` fences exact replay. All four fields deliberately /// preserve the Server's existing optionality. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", ts(optional_fields = nullable))] #[serde(deny_unknown_fields)] @@ -3461,7 +3880,7 @@ pub struct WorkingDirectoryCreateRequest { pub operation_id: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryListResponse { @@ -3470,7 +3889,7 @@ pub struct WorkingDirectoryListResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryDetailResponse { @@ -3481,7 +3900,72 @@ pub struct WorkingDirectoryDetailResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct HostSummary { + pub runtime_id: String, + pub host_id: String, + pub label: String, + pub kind: String, + pub status: String, + pub observed_at: String, + pub last_seen_at: Option, + pub os: String, + pub arch: String, + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct HostListResponse { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub limit: usize, + pub items: Vec, + pub source: String, + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct HostWorkerListResponse { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + pub limit: usize, + pub items: Vec, + pub source: String, + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CurrentWorkerWorkdirAttachRequest { + pub alias: String, + pub working_directory_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CurrentWorkerWorkdirAttachmentResponse { + pub workspace_id: String, + pub alias: String, + pub working_directory_id: String, + pub capabilities: workdir::WorkdirSessionCapabilities, + pub attached: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CurrentWorkerWorkdirOperationRequest { + pub target_workdir: String, + pub operation: workdir::http::WorkdirSessionOperation, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(transparent)] +pub struct CurrentWorkerWorkdirOperationResponse(pub workdir::http::WorkdirSessionOperationResult); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryCreateResponse { @@ -3489,6 +3973,20 @@ pub struct WorkingDirectoryCreateResponse { pub runtime_id: String, pub item: WorkingDirectorySummary, pub diagnostics: Vec, + #[serde(skip, default = "default_workdir_create_status")] + #[cfg_attr(feature = "typescript", ts(skip))] + #[schemars(skip)] + pub http_status: u16, +} + +const fn default_workdir_create_status() -> u16 { + 201 +} + +impl api_macros::HttpSuccess for WorkingDirectoryCreateResponse { + fn status_code(&self) -> u16 { + self.http_status + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -4962,7 +5460,7 @@ pub struct RuntimeConnectionTestResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkerWorkspaceSummary { pub visibility: String, @@ -4971,14 +5469,14 @@ pub struct WorkerWorkspaceSummary { pub workspace_id: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkerImplementationSummary { pub kind: String, pub display_hint: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkerCapabilitySummary { pub can_stop: bool, @@ -5149,7 +5647,7 @@ pub struct WorkspaceWorkerDiscoveryPage { /// One Workspace Worker Workdir attachment. `alias` is the stable Worker-local /// routing key; the nested Workdir id and display name are inventory metadata. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkerWorkdirAttachmentSummary { @@ -5170,7 +5668,7 @@ pub struct RuntimeWorkerWorkdirAttachmentSummary { /// `resource_key` is required here even though Runtime-internal Worker summaries /// do not carry one. The Workspace Server must resolve it from Workspace /// authority before constructing this response. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkerSummary { pub runtime_id: String, @@ -5190,6 +5688,7 @@ pub struct WorkerSummary { pub state: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional))] + #[schemars(with = "Option")] pub worker_state: Option, pub last_seen_at: Option, #[serde(default)] diff --git a/crates/server-api/src/repository_openapi_typescript.rs b/crates/server-api/src/repository_openapi_typescript.rs index ac459aa91..fa374443f 100644 --- a/crates/server-api/src/repository_openapi_typescript.rs +++ b/crates/server-api/src/repository_openapi_typescript.rs @@ -7,6 +7,11 @@ pub const GENERATOR_VERSION: &str = "1"; pub const GENERATOR_OPTIONS: &str = "repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output"; pub const INPUT_PATH: &str = "openapi/server-api.json"; pub const OUTPUT_PATH: &str = "web/workspace/src/lib/generated/repository-api.ts"; +pub const WORKDIR_GENERATOR_NAME: &str = "yoi-workdir-openapi-typescript"; +pub const WORKDIR_GENERATOR_VERSION: &str = "1"; +pub const WORKDIR_GENERATOR_OPTIONS: &str = + "workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output"; +pub const WORKDIR_OUTPUT_PATH: &str = "web/workspace/src/lib/generated/workdir-api.ts"; const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0; const MIN_SAFE_INTEGER: f64 = -9_007_199_254_740_991.0; @@ -72,6 +77,48 @@ pub fn generate_repository_typescript(input: &str) -> Result Result { + let document: Value = serde_json::from_str(input) + .map_err(|error| GenerationError::invalid(format!("invalid OpenAPI JSON: {error}")))?; + let root = object(&document, "OpenAPI document")?; + if root.get("openapi").and_then(Value::as_str) != Some("3.1.0") { + return Err(GenerationError::invalid( + "Workdir TypeScript generation requires OpenAPI 3.1.0", + )); + } + + let info = object_field(root, "info", "OpenAPI document")?; + let contract_version = required_string(info, "version", "OpenAPI info")?; + let contract_digest = required_string(info, "x-yoi-source-digest", "OpenAPI info")?; + validate_contract_digest(contract_digest)?; + let schemas = object_field( + object_field(root, "components", "OpenAPI document")?, + "schemas", + "OpenAPI components", + )?; + let paths = object_field(root, "paths", "OpenAPI document")?; + let roots = validate_workdir_operations(paths)?; + let closure = schema_closure(schemas, &roots)?; + + let mut output = format!( + "// @generated by {WORKDIR_GENERATOR_NAME}@{WORKDIR_GENERATOR_VERSION}. Do not edit.\n\ +// Input: {INPUT_PATH}\n\ +// Input contract: {contract_version} ({contract_digest})\n\ +// Options: {WORKDIR_GENERATOR_OPTIONS}\n\ +// Output: {WORKDIR_OUTPUT_PATH}\n\ +// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check\n\n" + ); + for name in closure { + let schema = schemas + .get(&name) + .ok_or_else(|| GenerationError::invalid(format!("missing schema `{name}`")))?; + let declaration = render_schema(schema, &format!("schema `{name}`"))?; + output.push_str(&format!("export type {name} = {declaration};\n\n")); + } + output.pop(); + Ok(output) +} + fn validate_contract_digest(digest: &str) -> Result<(), GenerationError> { let Some(hex) = digest.strip_prefix("sha256:") else { return Err(GenerationError::invalid( @@ -89,8 +136,7 @@ fn validate_contract_digest(digest: &str) -> Result<(), GenerationError> { fn validate_repository_operations( paths: &Map, ) -> Result, GenerationError> { - let mut roots = BTreeSet::new(); - for operation in [ + let operations = [ OperationContract { path: "/api/repositories", method: "get", @@ -129,7 +175,184 @@ fn validate_repository_operations( request: None, successes: &[("200", "RepositoryDetailResponse")], }, - ] { + OperationContract { + path: "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", + method: "post", + operation_id: "repository_ssh_connection_probe", + request: Some("RepositorySshConnectionProbeRequest"), + successes: &[("200", "RepositorySshConnectionProbeResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", + method: "put", + operation_id: "repository_ssh_host_trust_confirm", + request: Some("ConfirmRepositorySshHostTrustRequest"), + successes: &[("200", "RepositorySshHostTrust")], + }, + OperationContract { + path: "/api/w/{workspace_id}/repositories/{repository_key}/log", + method: "get", + operation_id: "repository_log", + request: None, + successes: &[("200", "RepositoryLogResponse")], + }, + OperationContract { + path: "/api/repositories/{repository_key}/log", + method: "get", + operation_id: "repository_log_alias", + request: None, + successes: &[("200", "RepositoryLogResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/hosts", + method: "get", + operation_id: "host_list", + request: None, + successes: &[("200", "HostListResponse")], + }, + OperationContract { + path: "/api/hosts", + method: "get", + operation_id: "host_list_alias", + request: None, + successes: &[("200", "HostListResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/hosts/{host_id}/workers", + method: "get", + operation_id: "host_worker_list", + request: None, + successes: &[("200", "HostWorkerListResponse")], + }, + OperationContract { + path: "/api/hosts/{host_id}/workers", + method: "get", + operation_id: "host_worker_list_alias", + request: None, + successes: &[("200", "HostWorkerListResponse")], + }, + ]; + validate_operation_contracts(paths, &operations) +} + +fn validate_workdir_operations( + paths: &Map, +) -> Result, GenerationError> { + let operations = [ + OperationContract { + path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", + method: "get", + operation_id: "runtime_workdir_list", + request: None, + successes: &[("200", "WorkingDirectoryListResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", + method: "post", + operation_id: "runtime_workdir_create", + request: Some("WorkingDirectoryCreateRequest"), + successes: &[ + ("200", "WorkingDirectoryCreateResponse"), + ("201", "WorkingDirectoryCreateResponse"), + ], + }, + OperationContract { + path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", + method: "get", + operation_id: "runtime_workdir_detail", + request: None, + successes: &[("200", "WorkingDirectoryDetailResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", + method: "delete", + operation_id: "runtime_workdir_cleanup", + request: Some("WorkingDirectoryRemovalRequest"), + successes: &[("200", "WorkingDirectoryRemovalResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/workers/self/workdir-attachments", + method: "post", + operation_id: "current_worker_workdir_attach", + request: Some("CurrentWorkerWorkdirAttachRequest"), + successes: &[("200", "CurrentWorkerWorkdirAttachmentResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}", + method: "delete", + operation_id: "current_worker_workdir_detach", + request: None, + successes: &[("200", "CurrentWorkerWorkdirAttachmentResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/workers/self/workdir-session/operations", + method: "post", + operation_id: "current_worker_workdir_operation", + request: Some("CurrentWorkerWorkdirOperationRequest"), + successes: &[("200", "WorkdirSessionOperationResult")], + }, + OperationContract { + path: "/api/w/{workspace_id}/external-workdir-grants", + method: "post", + operation_id: "external_workdir_grant_create", + request: Some("ExternalWorkdirGrantCreateRequest"), + successes: &[("201", "ExternalWorkdirGrantResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", + method: "get", + operation_id: "external_workdir_grant_get", + request: None, + successes: &[("200", "ExternalWorkdirGrantResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", + method: "delete", + operation_id: "external_workdir_grant_revoke", + request: None, + successes: &[("200", "ExternalWorkdirGrantResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/working-directories", + method: "get", + operation_id: "workspace_workdir_list", + request: None, + successes: &[("200", "WorkingDirectoryListResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/working-directories", + method: "post", + operation_id: "workspace_workdir_create", + request: Some("WorkingDirectoryCreateRequest"), + successes: &[ + ("200", "WorkingDirectoryCreateResponse"), + ("201", "WorkingDirectoryCreateResponse"), + ], + }, + OperationContract { + path: "/api/w/{workspace_id}/working-directories/{working_directory_id}", + method: "get", + operation_id: "workspace_workdir_detail", + request: None, + successes: &[("200", "WorkingDirectoryDetailResponse")], + }, + OperationContract { + path: "/api/w/{workspace_id}/working-directories/{working_directory_id}", + method: "delete", + operation_id: "workspace_workdir_cleanup", + request: Some("WorkingDirectoryRemovalRequest"), + successes: &[("200", "WorkingDirectoryRemovalResponse")], + }, + ]; + validate_operation_contracts(paths, &operations) +} + +fn validate_operation_contracts( + paths: &Map, + operations: &[OperationContract], +) -> Result, GenerationError> { + let mut roots = BTreeSet::new(); + for operation in operations { let path = object_field(paths, operation.path, "OpenAPI paths")?; let value = object_field(path, operation.method, operation.path)?; if value.get("operationId").and_then(Value::as_str) != Some(operation.operation_id) { @@ -167,10 +390,13 @@ fn validate_repository_operations( &format!("{} response {status}", operation.operation_id), )?; let schema = json_content_schema(response, operation.operation_id)?; - let expected = successes - .get(status.as_str()) - .copied() - .unwrap_or("RepositoryApiError"); + let expected = successes.get(status.as_str()).copied().unwrap_or_else(|| { + if operation.operation_id == "current_worker_workdir_operation" { + "WorkdirOperationApiError" + } else { + "RepositoryApiError" + } + }); require_schema_ref(schema, expected, operation.operation_id)?; roots.insert(expected.to_owned()); } @@ -315,6 +541,27 @@ fn literal_matches_type(kind: &str, value: &Value) -> bool { ) } +fn tagged_object_discriminator(value: &Value) -> Option<(String, String)> { + let schema = value.as_object()?; + if schema.get("type")?.as_str()? != "object" + || schema.get("additionalProperties") != Some(&Value::Bool(false)) + { + return None; + } + let required = schema.get("required")?.as_array()?; + let properties = schema.get("properties")?.as_object()?; + properties.iter().find_map(|(name, property)| { + if !required + .iter() + .any(|required| required.as_str() == Some(name)) + { + return None; + } + let literal = property.as_object()?.get("const")?.as_str()?; + Some((name.clone(), literal.to_owned())) + }) +} + fn literal_schema_matches_type(schema: &Map, kind: &str) -> bool { if let Some(constant) = schema.get("const") { return literal_matches_type(kind, constant); @@ -328,6 +575,14 @@ fn literal_schema_matches_type(schema: &Map, kind: &str) -> bool } fn render_schema(value: &Value, context: &str) -> Result { + if value == &Value::Bool(true) { + return Ok("unknown".to_owned()); + } + if value == &Value::Bool(false) { + return Err(GenerationError::invalid(format!( + "{context} rejects every value" + ))); + } let schema = object(value, context)?; if schema.contains_key("$ref") { let reference = schema_reference(value, context)?; @@ -403,6 +658,34 @@ fn render_schema(value: &Value, context: &str) -> Result>>(); + if let Some(discriminators) = tagged_discriminators { + if outer_type.is_some() { + return Err(GenerationError::invalid(format!( + "{context} combines a tagged object union with an outer type" + ))); + } + let Some((discriminator_name, _)) = discriminators.first() else { + unreachable!("empty oneOf rejected above"); + }; + let mut seen = BTreeSet::new(); + for (name, literal) in &discriminators { + if name != discriminator_name || !seen.insert(literal.clone()) { + return Err(GenerationError::invalid(format!( + "{context} contains overlapping tagged oneOf branches" + ))); + } + } + return branches + .iter() + .map(|branch| render_schema(branch, context)) + .collect::, _>>() + .map(|branches| branches.join(" | ")); + } + let mut rendered = Vec::with_capacity(branches.len()); let mut seen_literals = BTreeSet::new(); for branch in branches { @@ -438,12 +721,12 @@ fn render_schema(value: &Value, context: &str) -> Result { ensure_schema_keywords(schema, &["format", "maximum", "minimum", "type"], context)?; let format = match schema.get("format") { - Some(Value::String(format)) if format == "uint64" => Some(format.as_str()), + Some(Value::String(format)) + if matches!( + format.as_str(), + "uint" | "uint8" | "uint16" | "uint32" | "uint64" | "int32" + ) => + { + Some(format.as_str()) + } Some(Value::String(format)) => { return Err(GenerationError::invalid(format!( "{context} uses unsupported integer format `{format}`" @@ -545,12 +835,16 @@ fn render_typed_schema( }; let minimum = schema.get("minimum").and_then(Value::as_f64); let maximum = schema.get("maximum").and_then(Value::as_f64); - let expected_minimum = if format == Some("uint64") { - 0.0 - } else { - MIN_SAFE_INTEGER + let (expected_minimum, expected_maximum) = match format { + Some("uint") | Some("uint64") => (0.0, MAX_SAFE_INTEGER), + Some("uint8") => (0.0, u8::MAX as f64), + Some("uint16") => (0.0, u16::MAX as f64), + Some("uint32") => (0.0, u32::MAX as f64), + Some("int32") => (i32::MIN as f64, i32::MAX as f64), + None => (MIN_SAFE_INTEGER, MAX_SAFE_INTEGER), + Some(_) => unreachable!("integer format validated above"), }; - if minimum != Some(expected_minimum) || maximum != Some(MAX_SAFE_INTEGER) { + if minimum != Some(expected_minimum) || maximum != Some(expected_maximum) { return Err(GenerationError::invalid(format!( "{context} integer range does not match its JavaScript safe-number mapping" ))); @@ -558,11 +852,35 @@ fn render_typed_schema( Ok("number".to_owned()) } "array" => { - ensure_schema_keywords(schema, &["items", "type"], context)?; + ensure_schema_keywords(schema, &["items", "maxItems", "minItems", "type"], context)?; let items = schema.get("items").ok_or_else(|| { GenerationError::invalid(format!("{context} array has no item schema")) })?; - Ok(format!("Array<{}>", render_schema(items, context)?)) + let item = render_schema(items, context)?; + match (schema.get("minItems"), schema.get("maxItems")) { + (None, None) => Ok(format!("Array<{item}>")), + (Some(minimum), Some(maximum)) if minimum == maximum => { + let length = minimum.as_u64().ok_or_else(|| { + GenerationError::invalid(format!( + "{context} fixed array length must be a non-negative integer" + )) + })?; + if length > 64 { + return Err(GenerationError::invalid(format!( + "{context} fixed array length exceeds the generator limit" + ))); + } + Ok(format!( + "[{}]", + std::iter::repeat_n(item, length as usize) + .collect::>() + .join(", ") + )) + } + _ => Err(GenerationError::invalid(format!( + "{context} contains unsupported variable array bounds" + ))), + } } "object" => { ensure_schema_keywords( @@ -778,13 +1096,13 @@ mod tests { ); let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); - document["components"]["schemas"]["RepositoryListResponse"]["properties"]["items"]["maxItems"] = - serde_json::json!(1); + document["components"]["schemas"]["RepositoryListResponse"]["properties"]["items"]["uniqueItems"] = + serde_json::json!(true); let error = generate_repository_typescript(&document.to_string()).unwrap_err(); assert!( error .to_string() - .contains("unsupported schema keyword `maxItems`") + .contains("unsupported schema keyword `uniqueItems`") ); } diff --git a/crates/workdir/Cargo.toml b/crates/workdir/Cargo.toml index 22c70a495..b5b7a99c8 100644 --- a/crates/workdir/Cargo.toml +++ b/crates/workdir/Cargo.toml @@ -13,6 +13,7 @@ async-trait.workspace = true fs-operation.workspace = true manifest.workspace = true reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"], optional = true } +schemars.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true sha2.workspace = true diff --git a/crates/workdir/src/http.rs b/crates/workdir/src/http.rs index 00fa2330f..0a3f7e8aa 100644 --- a/crates/workdir/src/http.rs +++ b/crates/workdir/src/http.rs @@ -5,6 +5,7 @@ //! optional so Runtime servers can share these DTOs without depending on a //! client stack. +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ @@ -16,7 +17,7 @@ use crate::{ }; /// Opaque Runtime-owned identifier for one ephemeral Workdir session. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(transparent)] pub struct WorkdirSessionId(String); @@ -37,7 +38,7 @@ impl WorkdirSessionId { } /// Open a fresh session for a persisted Workdir identity. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct OpenWorkdirSessionRequest { /// Optional Runtime Worker whose persisted binding establishes workspace /// ownership of the Workdir. Runtime servers reject cross-workspace owners. @@ -45,7 +46,7 @@ pub struct OpenWorkdirSessionRequest { pub owner_worker_id: Option, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct OpenWorkdirSessionResponse { pub session_id: WorkdirSessionId, pub workdir_id: WorkdirId, @@ -53,7 +54,7 @@ pub struct OpenWorkdirSessionResponse { } /// One provider-side Workdir operation. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde( tag = "operation", content = "request", @@ -77,14 +78,14 @@ pub enum WorkdirSessionOperation { } /// Wire envelope for one provider operation. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkdirSessionOperationRequest { pub operation: WorkdirSessionOperation, } /// Typed result paired with [`WorkdirSessionOperation`]. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde( tag = "operation", content = "result", @@ -174,7 +175,7 @@ pub async fn dispatch_workdir_session_operation( } /// Stable, host-path-free error code crossing the Runtime boundary. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkdirTransportErrorCode { NotFound, @@ -238,7 +239,7 @@ impl WorkdirTransportErrorCode { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct WorkdirTransportError { pub code: WorkdirTransportErrorCode, pub message: String, diff --git a/crates/workdir/src/lib.rs b/crates/workdir/src/lib.rs index 6a13a5b46..e162b8d9e 100644 --- a/crates/workdir/src/lib.rs +++ b/crates/workdir/src/lib.rs @@ -20,6 +20,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use async_trait::async_trait; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; @@ -45,7 +46,7 @@ pub use scope::{ }; /// Persistent, opaque identity of one materialized Workdir. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] pub struct Workdir { id: WorkdirId, } @@ -63,7 +64,7 @@ impl Workdir { } /// Opaque Workdir identifier assigned by the materialization authority. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(transparent)] pub struct WorkdirId(String); @@ -79,7 +80,7 @@ impl std::fmt::Display for WorkdirId { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkdirSessionCapability { Read, @@ -90,7 +91,7 @@ pub enum WorkdirSessionCapability { Command, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct WorkdirSessionCapabilities { bits: u8, } diff --git a/crates/workdir/src/operation.rs b/crates/workdir/src/operation.rs index 5af8b54a3..c0c242774 100644 --- a/crates/workdir/src/operation.rs +++ b/crates/workdir/src/operation.rs @@ -1,15 +1,18 @@ use std::path::PathBuf; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(transparent)] pub struct CommandHandle(pub String); -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CommandRequest { pub command: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub timeout_secs: u64, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub output_limit: usize, /// Workdir-relative command directory. Providers validate it against the /// active session before process start. @@ -25,15 +28,17 @@ pub struct CommandRequest { pub tool_call_id: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CommandOutputRequest { pub handle: CommandHandle, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub cursor: usize, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub limit: usize, pub wait: bool, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CommandStatus { Running, @@ -43,35 +48,41 @@ pub enum CommandStatus { Cancelled, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CommandStream { Stdout, Stderr, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] pub struct CommandStreamSlice { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub start_offset: u64, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub end_offset: u64, pub content: String, pub truncated: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CommandSnapshot { pub command_id: String, pub tool_call_id: Option, pub status: CommandStatus, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub started_at_ms: u64, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub observed_at_ms: u64, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub last_output_at_ms: Option, pub stdout: CommandStreamSlice, pub stderr: CommandStreamSlice, + #[schemars(range(min = -2147483648_i32, max = 2147483647_i32))] pub exit_code: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum CommandEvent { Started { @@ -97,12 +108,14 @@ pub enum CommandEvent { }, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CommandOutput { pub status: CommandStatus, + #[schemars(range(min = -2147483648_i32, max = 2147483647_i32))] pub exit_code: Option, pub timed_out: bool, pub content: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] pub next_cursor: Option, pub truncated: bool, /// Complete output retained by the provider when `truncated` is true. diff --git a/crates/workdir/src/scope.rs b/crates/workdir/src/scope.rs index 7eadad4e9..fb5aabe74 100644 --- a/crates/workdir/src/scope.rs +++ b/crates/workdir/src/scope.rs @@ -9,6 +9,7 @@ use fs_operation::{ ListResult, ReadRequest, ReadResult, StatRequest, StatResult, WriteRequest, WriteResult, }; use manifest::SymlinkPolicy; +use schemars::JsonSchema; use tokio::sync::broadcast; const MAX_SCOPED_COMMANDS: usize = 16; @@ -19,25 +20,26 @@ use crate::{ WorkdirSessionCapabilities, WorkdirSessionCapability, WorkdirSessionHandle, }; -#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkdirToolScopePermission { Read, Write, } -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkdirToolScopeRule { pub target: FsPath, pub permission: WorkdirToolScopePermission, pub recursive: bool, #[serde(default)] + #[schemars(with = "String")] pub symlink_policy: SymlinkPolicy, } /// Provider-side check for one operation under an attenuated tool scope. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkdirScopeAuthorizationRequest { pub rules: Vec, @@ -46,14 +48,14 @@ pub struct WorkdirScopeAuthorizationRequest { } /// Provider-side overlap comparison that keeps resolved host paths private. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkdirScopeOverlapRequest { pub left: WorkdirToolScopeRule, pub right: WorkdirToolScopeRule, } -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkdirToolScope { pub rules: Vec, diff --git a/crates/workdir/src/workspace.rs b/crates/workdir/src/workspace.rs index c945accd4..2e7c57582 100644 --- a/crates/workdir/src/workspace.rs +++ b/crates/workdir/src/workspace.rs @@ -4,9 +4,10 @@ //! transport projections. They intentionally do not expose provider/session //! handles, host paths, Runtime URLs, or credentials. +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectoryMaterializerKind { #[default] @@ -14,7 +15,7 @@ pub enum WorkingDirectoryMaterializerKind { ClientHostedExternal, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectoryStatusKind { Active, @@ -42,7 +43,7 @@ impl std::fmt::Display for WorkingDirectoryStatusKind { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryCleanupTarget { pub kind: String, @@ -50,7 +51,7 @@ pub struct WorkingDirectoryCleanupTarget { pub repository_key: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryOccupancy { pub runtime_id: String, @@ -59,7 +60,7 @@ pub struct WorkingDirectoryOccupancy { pub linked_at: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct RuntimeWorkingDirectoryCleanupTarget { pub kind: String, @@ -67,7 +68,7 @@ pub struct RuntimeWorkingDirectoryCleanupTarget { pub repository_id: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct RuntimeWorkingDirectorySummary { pub working_directory_id: String, @@ -99,14 +100,14 @@ pub struct RuntimeWorkingDirectorySummary { pub occupied_by: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] pub enum WorkingDirectorySource { Repository { repository_key: String }, ExternalGrant { grant_id: String }, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct WorkingDirectorySummary { pub working_directory_id: String, @@ -146,7 +147,9 @@ impl WorkingDirectorySummary { pub use WorkingDirectoryMaterializerKind as MaterializerKind; /// Stable Workspace identity for a Worker hosted by a Runtime. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, +)] #[serde(deny_unknown_fields)] pub struct RuntimeWorkerRef { pub runtime_id: String, @@ -164,7 +167,7 @@ impl RuntimeWorkerRef { } /// Immutable materialization provenance retained by Workspace inventory. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryProvenance { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -179,7 +182,7 @@ pub struct WorkingDirectoryProvenance { } /// Latest provider-neutral observation attached to Workspace inventory. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkingDirectoryCurrentObservation { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -247,7 +250,7 @@ mod tests { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct WorkspaceWorkdirSessionOperationRequest { /// Worker-local attachment alias selected by the calling tool router. diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index f1d80a494..dfe5d72b9 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -291,6 +291,23 @@ impl From for server_api::Diagnostic { } } +impl From for server_api::HostSummary { + fn from(host: HostSummary) -> Self { + Self { + runtime_id: host.runtime_id, + host_id: host.host_id, + label: host.label, + kind: host.kind, + status: host.status, + observed_at: host.observed_at, + last_seen_at: host.last_seen_at, + os: host.os, + arch: host.arch, + diagnostics: host.diagnostics.into_iter().map(Into::into).collect(), + } + } +} + impl From for server_api::RuntimeSourceSummary { fn from(source: RuntimeSourceSummary) -> Self { let kind = match source.kind { diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 4d5bb7ee4..91e79e95a 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -3641,11 +3641,13 @@ fn build_server_auth_router(api: ServerAuthApi) -> Router { fn server_request_context( actor: Option, + worker_source: Option, origin: Option, headers: &HeaderMap, ) -> server_api::ServerRequestContext { server_api::ServerRequestContext { actor, + worker_source, origin, transport_headers: headers .iter() @@ -3683,7 +3685,7 @@ fn contract_request_headers( async fn attach_server_origin_context(mut request: Request, next: Next) -> Response { let actor = request.extensions().get::().cloned(); let origin = request_origin(request.headers()); - let context = server_request_context(actor, origin, request.headers()); + let context = server_request_context(actor, None, origin, request.headers()); request.extensions_mut().insert(context); next.run(request).await } @@ -3708,8 +3710,15 @@ async fn attach_server_request_context( Err(error) => return server_error_response(error), } }; + let worker_source = request + .extensions() + .get::() + .map(|source| server_api::ServerWorkerSource { + runtime_id: source.runtime_id.clone(), + worker_id: source.worker_id.clone(), + }); let origin = request_origin(request.headers()); - let context = server_request_context(actor, origin, request.headers()); + let context = server_request_context(actor, worker_source, origin, request.headers()); request.extensions_mut().insert(context); next.run(request).await } @@ -3865,6 +3874,62 @@ fn generated_workspace_contract_router(service: ServerApiContractService) -> Rou .merge(server_api::server_api_axum::repository_create( service.clone(), )) + .merge(server_api::server_api_axum::repository_ssh_connection_probe(service.clone())) + .merge(server_api::server_api_axum::repository_ssh_host_trust_confirm(service.clone())) + .merge(server_api::server_api_axum::repository_log(service.clone())) + .merge(server_api::server_api_axum::repository_log_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::host_list(service.clone())) + .merge(server_api::server_api_axum::host_list_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::host_worker_list( + service.clone(), + )) + .merge(server_api::server_api_axum::host_worker_list_alias( + service.clone(), + )) + .merge(server_api::server_api_axum::runtime_workdir_list( + service.clone(), + )) + .merge(server_api::server_api_axum::runtime_workdir_create( + service.clone(), + )) + .merge(server_api::server_api_axum::runtime_workdir_detail( + service.clone(), + )) + .merge(server_api::server_api_axum::runtime_workdir_cleanup( + service.clone(), + )) + .merge(server_api::server_api_axum::current_worker_workdir_attach( + service.clone(), + )) + .merge(server_api::server_api_axum::current_worker_workdir_detach( + service.clone(), + )) + .merge(server_api::server_api_axum::current_worker_workdir_operation(service.clone())) + .merge(server_api::server_api_axum::external_workdir_grant_create( + service.clone(), + )) + .merge(server_api::server_api_axum::external_workdir_grant_get( + service.clone(), + )) + .merge(server_api::server_api_axum::external_workdir_grant_revoke( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_workdir_list( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_workdir_create( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_workdir_detail( + service.clone(), + )) + .merge(server_api::server_api_axum::workspace_workdir_cleanup( + service.clone(), + )) .merge(server_api::server_api_axum::ticket_list_alias( service.clone(), )) @@ -6103,6 +6168,451 @@ impl server_api::ServerApi for ServerApiContractService { .map_err(ApiError::into_repository_api_error)?; project_contract_dto(&response) } + + async fn repository_ssh_connection_probe( + &self, + actor: RequestActor, + workspace_id: String, + repository_key: String, + request: server_api::RepositorySshConnectionProbeRequest, + ) -> std::result::Result< + server_api::RepositorySshConnectionProbeResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_probe_repository_ssh_connection( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryPath { + workspace_id, + repository_key, + }), + Extension(actor), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn repository_ssh_host_trust_confirm( + &self, + actor: RequestActor, + workspace_id: String, + repository_key: String, + request: server_api::ConfirmRepositorySshHostTrustRequest, + ) -> std::result::Result + { + let Json(response) = scoped_confirm_repository_ssh_host_trust( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryPath { + workspace_id, + repository_key, + }), + Extension(actor), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn repository_log( + &self, + workspace_id: String, + repository_key: String, + query: server_api::RepositoryLogQuery, + ) -> std::result::Result + { + let Json(response) = scoped_repository_log( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRepositoryPath { + workspace_id, + repository_key, + }), + Query(LogQuery { limit: query.limit }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn repository_log_alias( + &self, + repository_key: String, + query: server_api::RepositoryLogQuery, + ) -> std::result::Result + { + let Json(response) = repository_log( + State(self.workspace_api()?.clone()), + AxumPath(repository_key), + Query(LogQuery { limit: query.limit }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn host_list( + &self, + workspace_id: String, + ) -> std::result::Result { + let Json(response) = scoped_list_hosts( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::HostListResponse { + workspace_id: response.workspace_id, + limit: response.limit, + items: response.items.into_iter().map(Into::into).collect(), + source: response.source, + diagnostics: response.diagnostics.into_iter().map(Into::into).collect(), + }) + } + + async fn host_list_alias( + &self, + ) -> std::result::Result { + let Json(response) = list_hosts(State(self.workspace_api()?.clone())) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::HostListResponse { + workspace_id: response.workspace_id, + limit: response.limit, + items: response.items.into_iter().map(Into::into).collect(), + source: response.source, + diagnostics: response.diagnostics.into_iter().map(Into::into).collect(), + }) + } + + async fn host_worker_list( + &self, + workspace_id: String, + host_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_list_host_workers( + State(self.workspace_api()?.clone()), + AxumPath(ScopedHostPath { + workspace_id, + host_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn host_worker_list_alias( + &self, + host_id: String, + ) -> std::result::Result + { + let Json(response) = + list_host_workers(State(self.workspace_api()?.clone()), AxumPath(host_id)) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn runtime_workdir_list( + &self, + workspace_id: String, + runtime_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_list_runtime_working_directories( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn runtime_workdir_create( + &self, + workspace_id: String, + runtime_id: String, + request: server_api::WorkingDirectoryCreateRequest, + ) -> std::result::Result< + server_api::WorkingDirectoryCreateResponse, + server_api::RepositoryApiError, + > { + let (_, Json(response)) = scoped_create_runtime_working_directory( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn runtime_workdir_detail( + &self, + workspace_id: String, + runtime_id: String, + working_directory_id: String, + ) -> std::result::Result< + server_api::WorkingDirectoryDetailResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_runtime_working_directory_detail( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkingDirectoryPath { + workspace_id, + runtime_id, + working_directory_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn runtime_workdir_cleanup( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + runtime_id: String, + working_directory_id: String, + request: server_api::WorkingDirectoryRemovalRequest, + ) -> std::result::Result< + server_api::WorkingDirectoryRemovalResponse, + server_api::RepositoryApiError, + > { + cleanup_runtime_working_directory_contract( + self.workspace_api()?.clone(), + workspace_id, + runtime_id, + working_directory_id, + context, + request, + ) + .await + .map_err(ApiError::into_repository_api_error) + } + + async fn current_worker_workdir_attach( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::CurrentWorkerWorkdirAttachRequest, + ) -> std::result::Result< + server_api::CurrentWorkerWorkdirAttachmentResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_attach_current_worker_workdir( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context)?, + Json(AttachCurrentWorkerWorkdirRequest { + alias: request.alias, + working_directory_id: request.working_directory_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::CurrentWorkerWorkdirAttachmentResponse { + workspace_id: response.workspace_id, + alias: response.alias, + working_directory_id: response.working_directory_id, + capabilities: response.capabilities, + attached: response.attached, + }) + } + + async fn current_worker_workdir_detach( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + alias: String, + ) -> std::result::Result< + server_api::CurrentWorkerWorkdirAttachmentResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_detach_current_worker_workdir( + State(self.workspace_api()?.clone()), + AxumPath((workspace_id, alias)), + contract_request_headers(&context)?, + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(server_api::CurrentWorkerWorkdirAttachmentResponse { + workspace_id: response.workspace_id, + alias: response.alias, + working_directory_id: response.working_directory_id, + capabilities: response.capabilities, + attached: response.attached, + }) + } + + async fn current_worker_workdir_operation( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + request: server_api::CurrentWorkerWorkdirOperationRequest, + ) -> std::result::Result< + server_api::CurrentWorkerWorkdirOperationResponse, + server_api::WorkdirOperationApiError, + > { + let Json(response) = scoped_execute_current_worker_workdir_operation( + State( + self.workspace_api() + .map_err(server_api::WorkdirOperationApiError::api)? + .clone(), + ), + AxumPath(ScopedWorkspacePath { workspace_id }), + contract_request_headers(&context) + .map_err(server_api::WorkdirOperationApiError::api)?, + Json(WorkspaceWorkdirSessionOperationRequest { + target_workdir: request.target_workdir, + operation: request.operation, + }), + ) + .await + .map_err(|error| match error { + WorkdirOperationApiError::Api(error) => { + server_api::WorkdirOperationApiError::api(error.into_repository_api_error()) + } + WorkdirOperationApiError::Provider(error) => { + server_api::WorkdirOperationApiError::provider(error) + } + })?; + Ok(server_api::CurrentWorkerWorkdirOperationResponse(response)) + } + + async fn external_workdir_grant_create( + &self, + actor: RequestActor, + workspace_id: String, + request: server_api::ExternalWorkdirGrantCreateRequest, + ) -> std::result::Result + { + let (_, Json(response)) = scoped_create_external_workdir_grant( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn external_workdir_grant_get( + &self, + actor: RequestActor, + workspace_id: String, + grant_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_get_external_workdir_grant( + State(self.workspace_api()?.clone()), + Extension(actor), + AxumPath((workspace_id, grant_id)), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn external_workdir_grant_revoke( + &self, + actor: RequestActor, + workspace_id: String, + grant_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_revoke_external_workdir_grant( + State(self.workspace_api()?.clone()), + Extension(actor), + AxumPath((workspace_id, grant_id)), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn workspace_workdir_list( + &self, + workspace_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_list_working_directories( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn workspace_workdir_create( + &self, + workspace_id: String, + request: server_api::WorkingDirectoryCreateRequest, + ) -> std::result::Result< + server_api::WorkingDirectoryCreateResponse, + server_api::RepositoryApiError, + > { + let (_, Json(response)) = scoped_create_working_directory( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn workspace_workdir_detail( + &self, + workspace_id: String, + working_directory_id: String, + ) -> std::result::Result< + server_api::WorkingDirectoryDetailResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_working_directory_detail( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkingDirectoryPath { + workspace_id, + working_directory_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + Ok(response) + } + + async fn workspace_workdir_cleanup( + &self, + context: server_api::ServerRequestContext, + workspace_id: String, + working_directory_id: String, + request: server_api::WorkingDirectoryRemovalRequest, + ) -> std::result::Result< + server_api::WorkingDirectoryRemovalResponse, + server_api::RepositoryApiError, + > { + cleanup_working_directory_contract( + self.workspace_api()?.clone(), + workspace_id, + working_directory_id, + context, + request, + ) + .await + .map_err(ApiError::into_repository_api_error) + } } fn build_inner_router(api: WorkspaceApi) -> Router { @@ -6110,62 +6620,15 @@ fn build_inner_router(api: WorkspaceApi) -> Router { let auth = build_server_auth_router(ServerAuthApi::from(&api)) .merge(generated_auth_contract_router(contract_service.clone())); let workspace = Router::new() - .route( - "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", - post(scoped_probe_repository_ssh_connection) - .put(scoped_confirm_repository_ssh_host_trust), - ) - .route("/api/repositories/{repository_key}/log", get(repository_log)) - .route( - "/api/w/{workspace_id}/repositories/{repository_key}/log", - get(scoped_repository_log), - ) - .route("/api/hosts", get(list_hosts)) - .route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts)) - .route( - "/api/w/{workspace_id}/runtime-config", - get(get_latest_workspace_runtime_config), - ) - .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}/workers/self/workdir-attachments", - post(scoped_attach_current_worker_workdir), - ) - .route( - "/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}", - delete(scoped_detach_current_worker_workdir), - ) - .route( - "/api/w/{workspace_id}/workers/self/workdir-session/operations", - post(scoped_execute_current_worker_workdir_operation), - ) - .route( - "/api/w/{workspace_id}/external-workdir-grants", - post(scoped_create_external_workdir_grant), - ) - .route( - "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", - get(scoped_get_external_workdir_grant) - .delete(scoped_revoke_external_workdir_grant), - ) + // WebSocket provider transport is intentionally outside the generated + // JSON REST contract. .route( "/api/w/{workspace_id}/external-workdir-grants/{grant_id}/provider", get(scoped_external_workdir_provider_ws), ) .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), + "/api/w/{workspace_id}/runtime-config", + get(get_latest_workspace_runtime_config), ) .route("/api/runtimes", get(list_runtimes)) .route( @@ -6381,11 +6844,6 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/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) .merge(generated_workspace_contract_router(contract_service)); @@ -13759,13 +14217,81 @@ async fn scoped_runtime_working_directory_detail( 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, - worker_source: Option>, - request_actor: Option>, - Json(request): Json, -) -> ApiResult> { +fn workdir_removal_source_actor( + worker_source: Option<(&str, &str)>, + request_actor: Option<&RequestActor>, +) -> ApiResult { + if let Some((runtime_id, worker_id)) = worker_source { + Ok(format!("worker:{runtime_id}:{worker_id}")) + } else if let Some(actor) = request_actor { + Ok(format!("account:{}", actor.account_id)) + } else { + Err(ApiError::from(Error::WorkspacePermissionDenied( + "Workdir removal requires authenticated source authority".to_string(), + ))) + } +} + +fn contract_workdir_removal_source_actor( + context: &server_api::ServerRequestContext, +) -> ApiResult { + workdir_removal_source_actor( + context + .worker_source + .as_ref() + .map(|source| (source.runtime_id.as_str(), source.worker_id.as_str())), + context.actor.as_ref(), + ) +} + +async fn cleanup_runtime_working_directory_contract( + api: WorkspaceApi, + workspace_id: String, + runtime_id: String, + working_directory_id: String, + context: server_api::ServerRequestContext, + request: WorkingDirectoryRemovalRequest, +) -> ApiResult { + let source_actor = contract_workdir_removal_source_actor(&context)?; + cleanup_runtime_working_directory( + api, + ScopedRuntimeWorkingDirectoryPath { + workspace_id, + runtime_id, + working_directory_id, + }, + source_actor, + request, + ) + .await +} + +async fn cleanup_working_directory_contract( + api: WorkspaceApi, + workspace_id: String, + working_directory_id: String, + context: server_api::ServerRequestContext, + request: WorkingDirectoryRemovalRequest, +) -> ApiResult { + let source_actor = contract_workdir_removal_source_actor(&context)?; + cleanup_working_directory( + api, + ScopedWorkingDirectoryPath { + workspace_id, + working_directory_id, + }, + source_actor, + request, + ) + .await +} + +async fn cleanup_runtime_working_directory( + api: WorkspaceApi, + path: ScopedRuntimeWorkingDirectoryPath, + source_actor: String, + request: WorkingDirectoryRemovalRequest, +) -> ApiResult { validate_workspace_scope(&api, &path.workspace_id)?; require_active_workspace_runtime_binding(&api, &path.runtime_id).await?; let registered_runtime = registered_workdir_runtime_id(&api, &path.working_directory_id)?; @@ -13774,22 +14300,12 @@ async fn scoped_cleanup_runtime_working_directory( "Workdir does not belong to the requested Runtime".to_string(), ))); } - let source_actor = if let Some(Extension(source)) = worker_source { - format!("worker:{}:{}", source.runtime_id, source.worker_id) - } else if let Some(Extension(actor)) = request_actor { - format!("account:{}", actor.account_id) - } else { - return Err(ApiError::from(Error::WorkspacePermissionDenied( - "Workdir removal requires authenticated source authority".to_string(), - ))); - }; execute_workdir_removal( &api, &path.working_directory_id, &source_actor, &request.reason, ) - .map(Json) .map_err(ApiError::from) } @@ -14736,30 +15252,19 @@ async fn scoped_working_directory_detail( } } -async fn scoped_cleanup_working_directory( - State(api): State, - AxumPath(path): AxumPath, - worker_source: Option>, - request_actor: Option>, - Json(request): Json, -) -> ApiResult> { +async fn cleanup_working_directory( + api: WorkspaceApi, + path: ScopedWorkingDirectoryPath, + source_actor: String, + request: WorkingDirectoryRemovalRequest, +) -> ApiResult { validate_workspace_scope(&api, &path.workspace_id)?; - let source_actor = if let Some(Extension(source)) = worker_source { - format!("worker:{}:{}", source.runtime_id, source.worker_id) - } else if let Some(Extension(actor)) = request_actor { - format!("account:{}", actor.account_id) - } else { - return Err(ApiError::from(Error::WorkspacePermissionDenied( - "Workdir removal requires authenticated source authority".to_string(), - ))); - }; execute_workdir_removal( &api, &path.working_directory_id, &source_actor, &request.reason, ) - .map(Json) .map_err(ApiError::from) } @@ -15022,6 +15527,7 @@ async fn create_workspace_working_directory( .expect("Repository Workdir detail has Runtime"), item: response.item, diagnostics: response.diagnostics, + http_status: StatusCode::OK.as_u16(), }), ) }); @@ -15217,6 +15723,7 @@ async fn create_workspace_working_directory( runtime_id: reserved.resolved_runtime_id, item: summary.into(), diagnostics: working_directory_diagnostics(result.diagnostics), + http_status: StatusCode::CREATED.as_u16(), }), )) } @@ -16894,7 +17401,7 @@ async fn scoped_worker_protocol_ws( async fn scoped_list_host_workers( State(api): State, AxumPath(path): AxumPath, -) -> ApiResult>> { +) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; list_host_workers(State(api), AxumPath(path.host_id)).await } @@ -20394,14 +20901,14 @@ fn protocol_error_event(message: impl Into) -> protocol::Event { async fn list_host_workers( State(api): State, AxumPath(host_id): AxumPath, -) -> ApiResult>> { +) -> 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())?; let items = project_observed_workspace_workers(&api, runtime_workers.items)?; - Ok(Json(server_api::ListResponse { + Ok(Json(server_api::HostWorkerListResponse { workspace_id: api.workspace_id().to_string(), limit, items, diff --git a/openapi/server-api.json b/openapi/server-api.json index b2eaccb9b..19327a623 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -473,6 +473,143 @@ }, "type": "object" }, + "CommandOutput": { + "properties": { + "content": { + "type": "string" + }, + "exit_code": { + "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, + "type": [ + "integer", + "null" + ] + }, + "next_cursor": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "output_path": { + "description": "Complete output retained by the provider when `truncated` is true.", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/CommandStatus" + }, + "timed_out": { + "type": "boolean" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "status", + "timed_out", + "content", + "truncated" + ], + "type": "object" + }, + "CommandOutputRequest": { + "properties": { + "cursor": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "handle": { + "type": "string" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "wait": { + "type": "boolean" + } + }, + "required": [ + "handle", + "cursor", + "limit", + "wait" + ], + "type": "object" + }, + "CommandRequest": { + "properties": { + "command": { + "type": "string" + }, + "cwd": { + "anyOf": [ + { + "$ref": "#/components/schemas/FsPath" + }, + { + "type": "null" + } + ], + "description": "Workdir-relative command directory. Providers validate it against the\nactive session before process start." + }, + "output_limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "spill_dir": { + "description": "Provider-local directory where complete output is retained when the\ninline result exceeds `output_limit`.", + "type": [ + "string", + "null" + ] + }, + "timeout_secs": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "tool_call_id": { + "description": "Optional caller-owned correlation id. Bash supplies its tool-call id so\nuser-facing command telemetry can update the corresponding Console row\nwithout exposing provider/session handles.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "timeout_secs", + "output_limit" + ], + "type": "object" + }, + "CommandStatus": { + "enum": [ + "running", + "completed", + "failed", + "timed_out", + "cancelled" + ], + "type": "string" + }, "CompiledFlowDefinition": { "additionalProperties": false, "properties": { @@ -844,6 +981,35 @@ ], "type": "object" }, + "ConfirmRepositorySshHostTrustRequest": { + "additionalProperties": false, + "properties": { + "expected_host_trust_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "host_key": { + "type": "string" + }, + "operation_id": { + "type": "string" + }, + "runtime_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "runtime_id", + "host_key" + ], + "type": "object" + }, "ConflictResolution": { "enum": [ "none", @@ -968,6 +1134,66 @@ ], "type": "object" }, + "CurrentWorkerWorkdirAttachRequest": { + "additionalProperties": false, + "properties": { + "alias": { + "type": "string" + }, + "working_directory_id": { + "type": "string" + } + }, + "required": [ + "alias", + "working_directory_id" + ], + "type": "object" + }, + "CurrentWorkerWorkdirAttachmentResponse": { + "additionalProperties": false, + "properties": { + "alias": { + "type": "string" + }, + "attached": { + "type": "boolean" + }, + "capabilities": { + "$ref": "#/components/schemas/WorkdirSessionCapabilities" + }, + "working_directory_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "alias", + "working_directory_id", + "capabilities", + "attached" + ], + "type": "object" + }, + "CurrentWorkerWorkdirOperationRequest": { + "additionalProperties": false, + "properties": { + "operation": { + "$ref": "#/components/schemas/WorkdirSessionOperation" + }, + "target_workdir": { + "type": "string" + } + }, + "required": [ + "target_workdir", + "operation" + ], + "type": "object" + }, "DefaultIntakeReadyBodyRequest": { "additionalProperties": false, "properties": { @@ -1219,6 +1445,76 @@ ], "type": "string" }, + "EditRequest": { + "additionalProperties": false, + "properties": { + "expected_hash": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "maxItems": 32, + "minItems": 32, + "type": "array" + }, + "new_string": { + "type": "string" + }, + "old_string": { + "type": "string" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "replace_all": { + "type": "boolean" + } + }, + "required": [ + "path", + "old_string", + "new_string", + "replace_all", + "expected_hash" + ], + "type": "object" + }, + "EditResult": { + "additionalProperties": false, + "properties": { + "bytes_written": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "content_hash": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "maxItems": 32, + "minItems": 32, + "type": "array" + }, + "replacements": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "replacements", + "bytes_written", + "content_hash" + ], + "type": "object" + }, "EffectivePromptCatalog": { "additionalProperties": false, "properties": { @@ -1256,6 +1552,15 @@ ], "type": "object" }, + "EntryKind": { + "enum": [ + "file", + "directory", + "symlink", + "other" + ], + "type": "string" + }, "EvidenceKind": { "description": "Extensible evidence kind tag used by staging source anchors.\n\nKnown values include `message`, `tool_call`, `tool_result`, `file_ref`,\n`ticket_ref`, and `objective_ref`, but callers may use newer bounded tags\nwithout changing the schema.", "type": "string" @@ -1354,6 +1659,80 @@ } ] }, + "ExternalWorkdirGrantCreateRequest": { + "additionalProperties": false, + "properties": { + "display_name": { + "type": "string" + }, + "provider_instance_id": { + "type": "string" + }, + "read_only": { + "type": "boolean" + }, + "ttl_seconds": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "provider_instance_id", + "display_name", + "ttl_seconds", + "read_only" + ], + "type": "object" + }, + "ExternalWorkdirGrantResponse": { + "additionalProperties": false, + "properties": { + "display_name": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "generation": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "grant_id": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "provider_instance_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "working_directory_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "grant_id", + "workspace_id", + "working_directory_id", + "provider_instance_id", + "display_name", + "permissions", + "expires_at", + "generation", + "status" + ], + "type": "object" + }, "ExtractAudit": { "properties": { "entry_range": { @@ -1542,6 +1921,10 @@ ], "type": "object" }, + "FsPath": { + "description": "Scope-checked filesystem path. Relative paths resolve below the bound\nWorkdir root; absolute paths require an explicit matching scope rule.", + "type": "string" + }, "GenerateRepositorySshCredentialRequest": { "additionalProperties": false, "properties": { @@ -1562,6 +1945,52 @@ ], "type": "object" }, + "GitCommitSummary": { + "additionalProperties": false, + "properties": { + "author_date": { + "type": "string" + }, + "author_email": { + "type": "string" + }, + "author_name": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "parents": { + "items": { + "type": "string" + }, + "type": "array" + }, + "refs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "short_hash": { + "type": "string" + }, + "summary": { + "type": "string" + } + }, + "required": [ + "hash", + "short_hash", + "summary", + "author_name", + "author_email", + "author_date", + "parents", + "refs" + ], + "type": "object" + }, "GitRemoteSummary": { "additionalProperties": false, "properties": { @@ -1613,6 +2042,155 @@ ], "type": "object" }, + "GlobRequest": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "pattern": { + "type": "string" + } + }, + "required": [ + "pattern", + "path", + "limit" + ], + "type": "object" + }, + "GlobResult": { + "additionalProperties": false, + "properties": { + "paths": { + "items": { + "$ref": "#/components/schemas/FsPath" + }, + "type": "array" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "paths", + "truncated" + ], + "type": "object" + }, + "GrepOutputMode": { + "enum": [ + "content", + "files_with_matches", + "count" + ], + "type": "string" + }, + "GrepRequest": { + "additionalProperties": false, + "properties": { + "after_context": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "before_context": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "case_insensitive": { + "type": "boolean" + }, + "file_type": { + "type": [ + "string", + "null" + ] + }, + "glob": { + "type": [ + "string", + "null" + ] + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "multiline": { + "type": "boolean" + }, + "offset": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "output_mode": { + "$ref": "#/components/schemas/GrepOutputMode" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "pattern": { + "type": "string" + } + }, + "required": [ + "pattern", + "path", + "case_insensitive", + "before_context", + "after_context", + "multiline", + "output_mode", + "limit", + "offset" + ], + "type": "object" + }, + "GrepResult": { + "additionalProperties": false, + "properties": { + "match_count": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "matched_files": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "output": { + "description": "Provider-rendered bounded grep report. Keeping rendering here avoids\ntransferring candidate files across a remote provider boundary.", + "type": "string" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "output", + "match_count", + "matched_files", + "truncated" + ], + "type": "object" + }, "HealthResponse": { "additionalProperties": false, "properties": { @@ -1625,6 +2203,133 @@ ], "type": "object" }, + "HostListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/HostSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "source", + "diagnostics" + ], + "type": "object" + }, + "HostSummary": { + "additionalProperties": false, + "properties": { + "arch": { + "type": "string" + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "host_id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "label": { + "type": "string" + }, + "last_seen_at": { + "type": [ + "string", + "null" + ] + }, + "observed_at": { + "type": "string" + }, + "os": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "runtime_id", + "host_id", + "label", + "kind", + "status", + "observed_at", + "os", + "arch", + "diagnostics" + ], + "type": "object" + }, + "HostWorkerListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/WorkerSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "source", + "diagnostics" + ], + "type": "object" + }, "InitialRepositoryIntent": { "additionalProperties": false, "description": "Initial Repository registration intent for Workspace creation.", @@ -1666,6 +2371,81 @@ ], "type": "object" }, + "ListEntry": { + "additionalProperties": false, + "properties": { + "kind": { + "$ref": "#/components/schemas/EntryKind" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "size": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "path", + "kind", + "size" + ], + "type": "object" + }, + "ListRequest": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + } + }, + "required": [ + "path", + "limit" + ], + "type": "object" + }, + "ListResult": { + "additionalProperties": false, + "properties": { + "entries": { + "items": { + "$ref": "#/components/schemas/ListEntry" + }, + "type": "array" + }, + "total_bytes": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "total_entries": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "entries", + "total_entries", + "total_bytes", + "truncated" + ], + "type": "object" + }, "MarkdownText": { "type": "string" }, @@ -4461,6 +5241,91 @@ ], "type": "object" }, + "ReadRequest": { + "additionalProperties": false, + "properties": { + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "max_bytes": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "offset": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + } + }, + "required": [ + "path", + "offset", + "limit", + "max_bytes" + ], + "type": "object" + }, + "ReadResult": { + "additionalProperties": false, + "properties": { + "bytes": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "type": "array" + }, + "content_hash": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "maxItems": 32, + "minItems": 32, + "type": "array" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "start_line": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "total_lines": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "path", + "bytes", + "start_line", + "total_lines", + "content_hash", + "truncated" + ], + "type": "object" + }, "RecordOperationAudit": { "properties": { "after_hash": { @@ -4738,6 +5603,65 @@ ], "type": "object" }, + "RepositoryLogQuery": { + "additionalProperties": false, + "properties": { + "limit": { + "default": null, + "format": "uint", + "maximum": 200, + "minimum": 1, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "RepositoryLogResponse": { + "additionalProperties": false, + "properties": { + "default_selector": { + "type": [ + "string", + "null" + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/GitCommitSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "repository_key": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "repository_key", + "limit", + "items", + "diagnostics" + ], + "type": "object" + }, "RepositoryObservedStatus": { "enum": [ "unverified", @@ -4806,6 +5730,81 @@ ], "type": "object" }, + "RepositorySshConnectionProbeRequest": { + "additionalProperties": false, + "properties": { + "runtime_id": { + "type": "string" + } + }, + "required": [ + "runtime_id" + ], + "type": "object" + }, + "RepositorySshConnectionProbeResponse": { + "additionalProperties": false, + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/RepositorySshHostKeyCandidate" + }, + "type": "array" + }, + "expected_host_trust_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "host_trust_id": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "port": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "repository_key": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "trust_state": { + "$ref": "#/components/schemas/RepositorySshConnectionTrustState" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "repository_key", + "runtime_id", + "hostname", + "port", + "trust_state", + "host_trust_id", + "candidates" + ], + "type": "object" + }, + "RepositorySshConnectionTrustState": { + "enum": [ + "untrusted", + "verified", + "changed" + ], + "type": "string" + }, "RepositorySshCredential": { "additionalProperties": false, "description": "Public metadata for one Workspace-scoped Repository SSH credential.\n\nSecret references and secret material are deliberately not part of this DTO.", @@ -4863,6 +5862,26 @@ ], "type": "object" }, + "RepositorySshHostKeyCandidate": { + "additionalProperties": false, + "properties": { + "algorithm": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "host_key": { + "type": "string" + } + }, + "required": [ + "algorithm", + "host_key", + "fingerprint" + ], + "type": "object" + }, "RepositorySshHostTrust": { "additionalProperties": false, "description": "Public metadata for an explicitly pinned SSH host key.", @@ -5826,6 +6845,41 @@ ], "type": "object" }, + "StatRequest": { + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/components/schemas/FsPath" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "StatResult": { + "additionalProperties": false, + "properties": { + "kind": { + "$ref": "#/components/schemas/EntryKind" + }, + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "size": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "path", + "kind", + "size" + ], + "type": "object" + }, "SubmitMergeRequestReviewRequest": { "additionalProperties": false, "properties": { @@ -8271,6 +9325,636 @@ }, "type": "object" }, + "WorkdirOperationApiError": { + "additionalProperties": false, + "description": "Error body for current-Worker Workdir operations.\n\nDomain/service failures preserve the existing Repository-style API envelope,\nwhile provider failures preserve the established typed Workdir transport\nenvelope and HTTP status classification.", + "properties": { + "code": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkdirTransportErrorCode" + }, + { + "type": "null" + } + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": [ + "array", + "null" + ] + }, + "error": { + "type": [ + "string", + "null" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "WorkdirScopeAuthorizationRequest": { + "additionalProperties": false, + "description": "Provider-side check for one operation under an attenuated tool scope.", + "properties": { + "path": { + "$ref": "#/components/schemas/FsPath" + }, + "permission": { + "$ref": "#/components/schemas/WorkdirToolScopePermission" + }, + "rules": { + "items": { + "$ref": "#/components/schemas/WorkdirToolScopeRule" + }, + "type": "array" + } + }, + "required": [ + "rules", + "path", + "permission" + ], + "type": "object" + }, + "WorkdirScopeOverlapRequest": { + "additionalProperties": false, + "description": "Provider-side overlap comparison that keeps resolved host paths private.", + "properties": { + "left": { + "$ref": "#/components/schemas/WorkdirToolScopeRule" + }, + "right": { + "$ref": "#/components/schemas/WorkdirToolScopeRule" + } + }, + "required": [ + "left", + "right" + ], + "type": "object" + }, + "WorkdirSessionCapabilities": { + "properties": { + "bits": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "bits" + ], + "type": "object" + }, + "WorkdirSessionOperation": { + "description": "One provider-side Workdir operation.", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "authorize_scope", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/WorkdirScopeAuthorizationRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "scope_rules_overlap", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/WorkdirScopeOverlapRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "stat", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/StatRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "read", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/ReadRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "write", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/WriteRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "edit", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/EditRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "list", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/ListRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "glob", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/GlobRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "grep", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/GrepRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_start", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/CommandRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_status", + "type": "string" + }, + "request": { + "type": "string" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_output", + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/CommandOutputRequest" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_cancel", + "type": "string" + }, + "request": { + "type": "string" + } + }, + "required": [ + "operation", + "request" + ], + "type": "object" + } + ] + }, + "WorkdirSessionOperationResult": { + "description": "Typed result paired with [`WorkdirSessionOperation`].", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "authorize_scope", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "scope_rules_overlap", + "type": "string" + }, + "result": { + "additionalProperties": false, + "properties": { + "overlaps": { + "type": "boolean" + } + }, + "required": [ + "overlaps" + ], + "type": "object" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "stat", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/StatResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "read", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/ReadResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "write", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/WriteResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "edit", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/EditResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "list", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/ListResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "glob", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/GlobResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "grep", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/GrepResult" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_start", + "type": "string" + }, + "result": { + "type": "string" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_status", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/CommandStatus" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_output", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/CommandOutput" + } + }, + "required": [ + "operation", + "result" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "operation": { + "const": "command_cancel", + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + } + ] + }, + "WorkdirToolScopePermission": { + "enum": [ + "read", + "write" + ], + "type": "string" + }, + "WorkdirToolScopeRule": { + "additionalProperties": false, + "properties": { + "permission": { + "$ref": "#/components/schemas/WorkdirToolScopePermission" + }, + "recursive": { + "type": "boolean" + }, + "symlink_policy": { + "default": "resolved", + "type": "string" + }, + "target": { + "$ref": "#/components/schemas/FsPath" + } + }, + "required": [ + "target", + "permission", + "recursive" + ], + "type": "object" + }, + "WorkdirTransportErrorCode": { + "description": "Stable, host-path-free error code crossing the Runtime boundary.", + "enum": [ + "not_found", + "conflict", + "unsupported", + "invalid_request", + "denied", + "out_of_scope", + "symlink_out_of_scope", + "broken_symlink", + "symlink_target_is_directory", + "read_only", + "is_directory", + "symlink_directory_not_traversed", + "unknown_command", + "unavailable", + "io", + "transport", + "internal" + ], + "type": "string" + }, + "WorkerCapabilitySummary": { + "properties": { + "can_spawn_followup": { + "type": "boolean" + }, + "can_stop": { + "type": "boolean" + } + }, + "required": [ + "can_stop", + "can_spawn_followup" + ], + "type": "object" + }, + "WorkerImplementationSummary": { + "properties": { + "display_hint": { + "type": "string" + }, + "kind": { + "type": "string" + } + }, + "required": [ + "kind", + "display_hint" + ], + "type": "object" + }, "WorkerLifecycleAudit": { "properties": { "consolidation": { @@ -8358,6 +10042,511 @@ ], "type": "string" }, + "WorkerSummary": { + "description": "Workspace-authoritative Worker projection.\n\n`resource_key` is required here even though Runtime-internal Worker summaries\ndo not carry one. The Workspace Server must resolve it from Workspace\nauthority before constructing this response.", + "properties": { + "capabilities": { + "$ref": "#/components/schemas/WorkerCapabilitySummary" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "display_name": { + "default": "", + "type": "string" + }, + "host_id": { + "type": "string" + }, + "implementation": { + "$ref": "#/components/schemas/WorkerImplementationSummary" + }, + "label": { + "type": "string" + }, + "last_seen_at": { + "type": [ + "string", + "null" + ] + }, + "pinned": { + "default": false, + "type": "boolean" + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "resource_key": { + "type": "string" + }, + "retention_state": { + "default": "", + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "singleton_key": { + "type": [ + "string", + "null" + ] + }, + "state": { + "description": "Runtime catalog lifecycle compatibility state. Live foreground state, when\navailable, is carried separately in `worker_state`.", + "type": "string" + }, + "tags": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "workdir_attachments": { + "items": { + "$ref": "#/components/schemas/WorkerWorkdirAttachmentSummary" + }, + "type": "array" + }, + "worker_id": { + "type": "string" + }, + "worker_state": true, + "workspace": { + "$ref": "#/components/schemas/WorkerWorkspaceSummary" + } + }, + "required": [ + "runtime_id", + "worker_id", + "resource_key", + "host_id", + "label", + "workspace", + "state", + "implementation", + "capabilities" + ], + "type": "object" + }, + "WorkerWorkdirAttachmentSummary": { + "additionalProperties": false, + "description": "One Workspace Worker Workdir attachment. `alias` is the stable Worker-local\nrouting key; the nested Workdir id and display name are inventory metadata.", + "properties": { + "alias": { + "type": "string" + }, + "working_directory": { + "$ref": "#/components/schemas/WorkingDirectorySummary" + } + }, + "required": [ + "alias", + "working_directory" + ], + "type": "object" + }, + "WorkerWorkspaceSummary": { + "properties": { + "identity": { + "type": "string" + }, + "visibility": { + "type": "string" + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "visibility", + "identity" + ], + "type": "object" + }, + "WorkingDirectoryCleanupTarget": { + "additionalProperties": false, + "properties": { + "kind": { + "type": "string" + }, + "repository_key": { + "type": "string" + }, + "working_directory_id": { + "type": "string" + } + }, + "required": [ + "kind", + "working_directory_id", + "repository_key" + ], + "type": "object" + }, + "WorkingDirectoryCreateRequest": { + "additionalProperties": false, + "description": "Browser/Rust-client Workdir materialization request.\n\n`runtime_id = None` requests Workspace default Runtime resolution and\n`operation_id = Some(_)` fences exact replay. All four fields deliberately\npreserve the Server's existing optionality.", + "properties": { + "display_name": { + "description": "Optional human-facing label, independent of every Worker attachment alias.", + "type": [ + "string", + "null" + ] + }, + "operation_id": { + "type": [ + "string", + "null" + ] + }, + "repository_key": { + "type": "string" + }, + "runtime_id": { + "type": [ + "string", + "null" + ] + }, + "selector": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "repository_key" + ], + "type": "object" + }, + "WorkingDirectoryCreateResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "item": { + "$ref": "#/components/schemas/WorkingDirectorySummary" + }, + "runtime_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "item", + "diagnostics" + ], + "type": "object" + }, + "WorkingDirectoryDetailResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "item": { + "$ref": "#/components/schemas/WorkingDirectorySummary" + }, + "runtime_id": { + "type": [ + "string", + "null" + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "item", + "diagnostics" + ], + "type": "object" + }, + "WorkingDirectoryListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/WorkingDirectorySummary" + }, + "type": "array" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "items", + "diagnostics" + ], + "type": "object" + }, + "WorkingDirectoryMaterializerKind": { + "description": "Public Workdir materializer classification.\n\nThe value identifies stable materialization provenance without exposing a\nprovider path, Runtime handle, or session identity.", + "enum": [ + "runtime_git_clone", + "client_hosted_external" + ], + "type": "string" + }, + "WorkingDirectoryOccupancy": { + "additionalProperties": false, + "description": "Durable Workspace occupancy projection for one Workdir.", + "properties": { + "display_name": { + "type": "string" + }, + "linked_at": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "runtime_id", + "worker_id", + "display_name", + "linked_at" + ], + "type": "object" + }, + "WorkingDirectoryRemovalDisposition": { + "enum": [ + "removed", + "retained", + "attention_required" + ], + "type": "string" + }, + "WorkingDirectoryRemovalRequest": { + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "WorkingDirectoryRemovalResponse": { + "additionalProperties": false, + "properties": { + "disposition": { + "$ref": "#/components/schemas/WorkingDirectoryRemovalDisposition" + }, + "failure_category": { + "type": [ + "string", + "null" + ] + }, + "retryable": { + "type": "boolean" + }, + "working_directory_id": { + "type": "string" + } + }, + "required": [ + "working_directory_id", + "disposition", + "retryable" + ], + "type": "object" + }, + "WorkingDirectorySource": { + "description": "Public Workdir source identity. Provider routing details and host paths are intentionally absent.", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "repository", + "type": "string" + }, + "repository_key": { + "type": "string" + } + }, + "required": [ + "kind", + "repository_key" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "grant_id": { + "type": "string" + }, + "kind": { + "const": "external_grant", + "type": "string" + } + }, + "required": [ + "kind", + "grant_id" + ], + "type": "object" + } + ] + }, + "WorkingDirectoryStatusKind": { + "enum": [ + "active", + "cleanup_pending", + "corrupted", + "not_found", + "unknown" + ], + "type": "string" + }, + "WorkingDirectorySummary": { + "additionalProperties": false, + "description": "Public, provider-neutral Workdir inventory projection.", + "properties": { + "cleanliness": { + "type": [ + "string", + "null" + ] + }, + "cleanup_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkingDirectoryCleanupTarget" + }, + { + "type": "null" + } + ] + }, + "creation_ref": { + "type": [ + "string", + "null" + ] + }, + "creation_selector": { + "type": [ + "string", + "null" + ] + }, + "creation_tree": { + "type": [ + "string", + "null" + ] + }, + "current_ref": { + "type": [ + "string", + "null" + ] + }, + "current_selector": { + "type": [ + "string", + "null" + ] + }, + "current_tree": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "description": "Optional human-facing label, never a routing key.", + "type": [ + "string", + "null" + ] + }, + "materializer_kind": { + "$ref": "#/components/schemas/WorkingDirectoryMaterializerKind" + }, + "observed_at_epoch_seconds": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "occupied_by": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkingDirectoryOccupancy" + }, + { + "type": "null" + } + ] + }, + "source": { + "$ref": "#/components/schemas/WorkingDirectorySource" + }, + "status": { + "$ref": "#/components/schemas/WorkingDirectoryStatusKind" + }, + "working_directory_id": { + "type": "string" + } + }, + "required": [ + "working_directory_id", + "source", + "materializer_kind", + "status" + ], + "type": "object" + }, "WorkspaceAuthConfig": { "description": "Browser authentication configuration exposed by the scoped Workspace summary.", "oneOf": [ @@ -9303,6 +11492,60 @@ ], "type": "object" }, + "WriteRequest": { + "additionalProperties": false, + "properties": { + "content": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "type": "array" + }, + "expected_hash": { + "items": { + "format": "uint8", + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "maxItems": 32, + "minItems": 32, + "type": [ + "array", + "null" + ] + }, + "path": { + "$ref": "#/components/schemas/FsPath" + } + }, + "required": [ + "path", + "content" + ], + "type": "object" + }, + "WriteResult": { + "properties": { + "bytes_written": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "created": { + "type": "boolean" + } + }, + "required": [ + "bytes_written", + "created" + ], + "type": "object" + }, "string": { "type": "string" } @@ -9322,7 +11565,7 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:0026ff3d0f1d267e4de3f154a14f4ef3f7554b603a376820f0775e5deb16a083" + "x-yoi-source-digest": "sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59" }, "openapi": "3.1.0", "paths": { @@ -9740,6 +11983,188 @@ } } }, + "/api/hosts": { + "get": { + "operationId": "host_list_alias", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HostListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/hosts/{host_id}/workers": { + "get": { + "operationId": "host_worker_list_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "host_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HostWorkerListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/objectives": { "get": { "operationId": "objective_list_alias", @@ -10010,6 +12435,103 @@ ] } }, + "/api/repositories/{repository_key}/log": { + "get": { + "operationId": "repository_log_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "repository_key", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RepositoryLogQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryLogResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/tickets": { "get": { "operationId": "ticket_list_alias", @@ -10585,6 +13107,315 @@ ] } }, + "/api/w/{workspace_id}/external-workdir-grants": { + "post": { + "operationId": "external_workdir_grant_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExternalWorkdirGrantCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExternalWorkdirGrantResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/external-workdir-grants/{grant_id}": { + "delete": { + "operationId": "external_workdir_grant_revoke", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "grant_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExternalWorkdirGrantResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "external_workdir_grant_get", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "grant_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExternalWorkdirGrantResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/flows": { "get": { "operationId": "flow_list", @@ -10939,6 +13770,210 @@ } } }, + "/api/w/{workspace_id}/hosts": { + "get": { + "operationId": "host_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HostListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/hosts/{host_id}/workers": { + "get": { + "operationId": "host_worker_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "host_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HostWorkerListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/internal/reviewer-child-sessions": { "post": { "operationId": "merge_request_reviewer_child_register", @@ -12909,6 +15944,919 @@ ] } }, + "/api/w/{workspace_id}/repositories/{repository_key}/log": { + "get": { + "operationId": "repository_log", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "repository_key", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RepositoryLogQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryLogResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test": { + "post": { + "operationId": "repository_ssh_connection_probe", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "repository_key", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshConnectionProbeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshConnectionProbeResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "put": { + "operationId": "repository_ssh_host_trust_confirm", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "repository_key", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfirmRepositorySshHostTrustRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositorySshHostTrust" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories": { + "get": { + "operationId": "runtime_workdir_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "runtime_workdir_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateResponse" + } + } + }, + "description": "Alternate successful response" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}": { + "delete": { + "operationId": "runtime_workdir_cleanup", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "working_directory_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryRemovalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryRemovalResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "runtime_workdir_detail", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "working_directory_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryDetailResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/settings": { "get": { "operationId": "workspace_metadata_settings", @@ -19626,6 +23574,872 @@ } } }, + "/api/w/{workspace_id}/workers/self/workdir-attachments": { + "post": { + "operationId": "current_worker_workdir_attach", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentWorkerWorkdirAttachRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentWorkerWorkdirAttachmentResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}": { + "delete": { + "operationId": "current_worker_workdir_detach", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "alias", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentWorkerWorkdirAttachmentResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/api/w/{workspace_id}/workers/self/workdir-session/operations": { + "post": { + "operationId": "current_worker_workdir_operation", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentWorkerWorkdirOperationRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirSessionOperationResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkdirOperationApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/api/w/{workspace_id}/working-directories": { + "get": { + "operationId": "workspace_workdir_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "workspace_workdir_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateResponse" + } + } + }, + "description": "Alternate successful response" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryCreateResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/working-directories/{working_directory_id}": { + "delete": { + "operationId": "workspace_workdir_cleanup", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "working_directory_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryRemovalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryRemovalResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "workspace_workdir_detail", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "working_directory_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingDirectoryDetailResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/workspace": { "get": { "operationId": "workspace_scoped", diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index c450bf1e9..2da845e3a 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,10 +1,12 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:0026ff3d0f1d267e4de3f154a14f4ef3f7554b603a376820f0775e5deb16a083) +// Input contract: 0.1.0 (sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59) // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Output: web/workspace/src/lib/generated/repository-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check +export type ConfirmRepositorySshHostTrustRequest = { expected_host_trust_revision?: number | null; host_key: string; operation_id: string; runtime_id: string; }; + export type CreateWorkspaceRepositoryRequest = { default_ref?: string | null; repository_key: string; source: string; }; export type CreateWorkspaceRepositoryResponse = { replayed: boolean; repository_key: string; workspace_id: string; }; @@ -13,10 +15,18 @@ export type Diagnostic = { code: string; message: string; severity: DiagnosticSe export type DiagnosticSeverity = "info" | "warning" | "error"; +export type GitCommitSummary = { author_date: string; author_email: string; author_name: string; hash: string; parents: Array; refs: Array; short_hash: string; summary: string; }; + export type GitRemoteSummary = { fetch_url: string; name: string; }; export type GitRepositorySummary = { branch?: string | null; dirty: boolean; head?: string | null; remotes: Array; status: string; }; +export type HostListResponse = { diagnostics: Array; items: Array; limit: number; source: string; workspace_id: string; }; + +export type HostSummary = { arch: string; diagnostics: Array; host_id: string; kind: string; label: string; last_seen_at?: string | null; observed_at: string; os: string; runtime_id: string; status: string; }; + +export type HostWorkerListResponse = { diagnostics: Array; items: Array; limit: number; source: string; workspace_id: string; }; + export type RepositoryApiError = { diagnostics?: Array; error: string; message: string; }; export type RepositoryDetailResponse = { item: RepositorySummary; source: string; workspace_id: string; }; @@ -25,10 +35,44 @@ export type RepositoryDiagnostic = { code: string; message: string; severity: st export type RepositoryListResponse = { diagnostics: Array; items: Array; source: string; workspace_id: string; }; +export type RepositoryLogResponse = { default_selector?: string | null; diagnostics: Array; items: Array; limit: number; repository_key: string; workspace_id: string; }; + export type RepositoryObservedStatus = "unverified" | "ready" | "invalid"; export type RepositorySource = { kind: RepositorySourceKind; uri: string; }; export type RepositorySourceKind = "local_path" | "file" | "ssh" | "https" | "invalid"; +export type RepositorySshConnectionProbeRequest = { runtime_id: string; }; + +export type RepositorySshConnectionProbeResponse = { candidates: Array; expected_host_trust_revision?: number | null; host_trust_id: string; hostname: string; port: number; repository_key: string; runtime_id: string; trust_state: RepositorySshConnectionTrustState; workspace_id: string; }; + +export type RepositorySshConnectionTrustState = "untrusted" | "verified" | "changed"; + +export type RepositorySshHostKeyCandidate = { algorithm: string; fingerprint: string; host_key: string; }; + +export type RepositorySshHostTrust = { created_at: string; current_revision: number; fingerprint: string; host_key: string; host_trust_id: string; hostname: string; key_algorithm: string; port: number; referenced_repositories?: Array; updated_at: string; workspace_id: string; }; + export type RepositorySummary = { default_selector?: string | null; diagnostics?: Array | null; git?: GitRepositorySummary | null; kind: string; observed_at?: string | null; observed_status: RepositoryObservedStatus; provider: string; record_authority: string; repository_key: string; source: RepositorySource; source_fingerprint: string; source_revision: number; }; + +export type WorkerCapabilitySummary = { can_spawn_followup: boolean; can_stop: boolean; }; + +export type WorkerImplementationSummary = { display_hint: string; kind: string; }; + +export type WorkerSummary = { capabilities: WorkerCapabilitySummary; diagnostics?: Array; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array; workdir_attachments?: Array; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; }; + +export type WorkerWorkdirAttachmentSummary = { alias: string; working_directory: WorkingDirectorySummary; }; + +export type WorkerWorkspaceSummary = { identity: string; visibility: string; workspace_id?: string | null; }; + +export type WorkingDirectoryCleanupTarget = { kind: string; repository_key: string; working_directory_id: string; }; + +export type WorkingDirectoryMaterializerKind = "runtime_git_clone" | "client_hosted_external"; + +export type WorkingDirectoryOccupancy = { display_name: string; linked_at: string; runtime_id: string; worker_id: string; }; + +export type WorkingDirectorySource = { kind: "repository"; repository_key: string; } | { grant_id: string; kind: "external_grant"; }; + +export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown"; + +export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_target?: WorkingDirectoryCleanupTarget | null; creation_ref?: string | null; creation_selector?: string | null; creation_tree?: string | null; current_ref?: string | null; current_selector?: string | null; current_tree?: string | null; display_name?: string | null; materializer_kind: WorkingDirectoryMaterializerKind; observed_at_epoch_seconds?: number | null; occupied_by?: WorkingDirectoryOccupancy | null; source: WorkingDirectorySource; status: WorkingDirectoryStatusKind; working_directory_id: string; }; diff --git a/web/workspace/src/lib/generated/workdir-api.ts b/web/workspace/src/lib/generated/workdir-api.ts index 385624fec..c9b61fc1c 100644 --- a/web/workspace/src/lib/generated/workdir-api.ts +++ b/web/workspace/src/lib/generated/workdir-api.ts @@ -1,34 +1,110 @@ -// Generated from server-api. Do not edit by hand. -// Regenerate: cargo run -q -p server-api --features typescript --example generate_workdir_api_types > web/workspace/src/lib/generated/workdir-api.ts +// @generated by yoi-workdir-openapi-typescript@1. Do not edit. +// Input: openapi/server-api.json +// Input contract: 0.1.0 (sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59) +// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output +// Output: web/workspace/src/lib/generated/workdir-api.ts +// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check + +export type CommandOutput = { content: string; exit_code?: number | null; next_cursor?: number | null; output_path?: string | null; status: CommandStatus; timed_out: boolean; truncated: boolean; }; + +export type CommandOutputRequest = { cursor: number; handle: string; limit: number; wait: boolean; }; + +export type CommandRequest = { command: string; cwd?: FsPath | null; output_limit: number; spill_dir?: string | null; timeout_secs: number; tool_call_id?: string | null; }; + +export type CommandStatus = "running" | "completed" | "failed" | "timed_out" | "cancelled"; + +export type CurrentWorkerWorkdirAttachRequest = { alias: string; working_directory_id: string; }; + +export type CurrentWorkerWorkdirAttachmentResponse = { alias: string; attached: boolean; capabilities: WorkdirSessionCapabilities; working_directory_id: string; workspace_id: string; }; + +export type CurrentWorkerWorkdirOperationRequest = { operation: WorkdirSessionOperation; target_workdir: string; }; + +export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; }; export type DiagnosticSeverity = "info" | "warning" | "error"; -export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, }; +export type EditRequest = { expected_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; new_string: string; old_string: string; path: FsPath; replace_all: boolean; }; + +export type EditResult = { bytes_written: number; content_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; replacements: number; }; + +export type EntryKind = "file" | "directory" | "symlink" | "other"; + +export type ExternalWorkdirGrantCreateRequest = { display_name: string; provider_instance_id: string; read_only: boolean; ttl_seconds: number; }; + +export type ExternalWorkdirGrantResponse = { display_name: string; expires_at: string; generation: number; grant_id: string; permissions: string; provider_instance_id: string; status: string; working_directory_id: string; workspace_id: string; }; + +export type FsPath = string; + +export type GlobRequest = { limit: number; path: FsPath; pattern: string; }; + +export type GlobResult = { paths: Array; truncated: boolean; }; + +export type GrepOutputMode = "content" | "files_with_matches" | "count"; + +export type GrepRequest = { after_context: number; before_context: number; case_insensitive: boolean; file_type?: string | null; glob?: string | null; limit: number; multiline: boolean; offset: number; output_mode: GrepOutputMode; path: FsPath; pattern: string; }; + +export type GrepResult = { match_count: number; matched_files: number; output: string; truncated: boolean; }; + +export type ListEntry = { kind: EntryKind; path: FsPath; size: number; }; + +export type ListRequest = { limit: number; path: FsPath; }; + +export type ListResult = { entries: Array; total_bytes: number; total_entries: number; truncated: boolean; }; + +export type ReadRequest = { limit: number; max_bytes: number; offset: number; path: FsPath; }; + +export type ReadResult = { bytes: Array; content_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; path: FsPath; start_line: number; total_lines: number; truncated: boolean; }; + +export type RepositoryApiError = { diagnostics?: Array; error: string; message: string; }; + +export type StatRequest = { path: FsPath; }; + +export type StatResult = { kind: EntryKind; path: FsPath; size: number; }; + +export type WorkdirOperationApiError = { code?: WorkdirTransportErrorCode | null; diagnostics?: Array | null; error?: string | null; message: string; }; + +export type WorkdirScopeAuthorizationRequest = { path: FsPath; permission: WorkdirToolScopePermission; rules: Array; }; + +export type WorkdirScopeOverlapRequest = { left: WorkdirToolScopeRule; right: WorkdirToolScopeRule; }; + +export type WorkdirSessionCapabilities = { bits: number; }; + +export type WorkdirSessionOperation = { operation: "authorize_scope"; request: WorkdirScopeAuthorizationRequest; } | { operation: "scope_rules_overlap"; request: WorkdirScopeOverlapRequest; } | { operation: "stat"; request: StatRequest; } | { operation: "read"; request: ReadRequest; } | { operation: "write"; request: WriteRequest; } | { operation: "edit"; request: EditRequest; } | { operation: "list"; request: ListRequest; } | { operation: "glob"; request: GlobRequest; } | { operation: "grep"; request: GrepRequest; } | { operation: "command_start"; request: CommandRequest; } | { operation: "command_status"; request: string; } | { operation: "command_output"; request: CommandOutputRequest; } | { operation: "command_cancel"; request: string; }; + +export type WorkdirSessionOperationResult = { operation: "authorize_scope"; } | { operation: "scope_rules_overlap"; result: { overlaps: boolean; }; } | { operation: "stat"; result: StatResult; } | { operation: "read"; result: ReadResult; } | { operation: "write"; result: WriteResult; } | { operation: "edit"; result: EditResult; } | { operation: "list"; result: ListResult; } | { operation: "glob"; result: GlobResult; } | { operation: "grep"; result: GrepResult; } | { operation: "command_start"; result: string; } | { operation: "command_status"; result: CommandStatus; } | { operation: "command_output"; result: CommandOutput; } | { operation: "command_cancel"; }; + +export type WorkdirToolScopePermission = "read" | "write"; + +export type WorkdirToolScopeRule = { permission: WorkdirToolScopePermission; recursive: boolean; symlink_policy?: string; target: FsPath; }; + +export type WorkdirTransportErrorCode = "not_found" | "conflict" | "unsupported" | "invalid_request" | "denied" | "out_of_scope" | "symlink_out_of_scope" | "broken_symlink" | "symlink_target_is_directory" | "read_only" | "is_directory" | "symlink_directory_not_traversed" | "unknown_command" | "unavailable" | "io" | "transport" | "internal"; + +export type WorkingDirectoryCleanupTarget = { kind: string; repository_key: string; working_directory_id: string; }; + +export type WorkingDirectoryCreateRequest = { display_name?: string | null; operation_id?: string | null; repository_key: string; runtime_id?: string | null; selector?: string | null; }; + +export type WorkingDirectoryCreateResponse = { diagnostics: Array; item: WorkingDirectorySummary; runtime_id: string; workspace_id: string; }; + +export type WorkingDirectoryDetailResponse = { diagnostics: Array; item: WorkingDirectorySummary; runtime_id?: string | null; workspace_id: string; }; + +export type WorkingDirectoryListResponse = { diagnostics: Array; items: Array; workspace_id: string; }; export type WorkingDirectoryMaterializerKind = "runtime_git_clone" | "client_hosted_external"; +export type WorkingDirectoryOccupancy = { display_name: string; linked_at: string; runtime_id: string; worker_id: string; }; + +export type WorkingDirectoryRemovalDisposition = "removed" | "retained" | "attention_required"; + +export type WorkingDirectoryRemovalRequest = { reason: string; }; + +export type WorkingDirectoryRemovalResponse = { disposition: WorkingDirectoryRemovalDisposition; failure_category?: string | null; retryable: boolean; working_directory_id: string; }; + +export type WorkingDirectorySource = { kind: "repository"; repository_key: string; } | { grant_id: string; kind: "external_grant"; }; + export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown"; -export type WorkingDirectoryCleanupTarget = { kind: string, working_directory_id: string, repository_key: string, }; +export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_target?: WorkingDirectoryCleanupTarget | null; creation_ref?: string | null; creation_selector?: string | null; creation_tree?: string | null; current_ref?: string | null; current_selector?: string | null; current_tree?: string | null; display_name?: string | null; materializer_kind: WorkingDirectoryMaterializerKind; observed_at_epoch_seconds?: number | null; occupied_by?: WorkingDirectoryOccupancy | null; source: WorkingDirectorySource; status: WorkingDirectoryStatusKind; working_directory_id: string; }; -export type WorkingDirectoryOccupancy = { runtime_id: string, worker_id: string, display_name: string, linked_at: string, }; +export type WriteRequest = { content: Array; expected_hash?: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number] | null; path: FsPath; }; -export type WorkingDirectorySource = { "kind": "repository", repository_key: string, } | { "kind": "external_grant", grant_id: string, }; - -export type WorkingDirectorySummary = { working_directory_id: string, -/** - * Optional human-facing label, never a routing key. - */ -display_name?: string | null, source: WorkingDirectorySource, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, }; - -export type WorkingDirectoryCreateRequest = { runtime_id?: string | null, -/** - * Optional human-facing label, independent of every Worker attachment alias. - */ -display_name?: string | null, repository_key: string, selector?: string | null, operation_id?: string | null, }; - -export type WorkingDirectoryListResponse = { workspace_id: string, items: Array, diagnostics: Array, }; - -export type WorkingDirectoryDetailResponse = { workspace_id: string, runtime_id?: string | null, item: WorkingDirectorySummary, diagnostics: Array, }; - -export type WorkingDirectoryCreateResponse = { workspace_id: string, runtime_id: string, item: WorkingDirectorySummary, diagnostics: Array, }; +export type WriteResult = { bytes_written: number; created: boolean; }; diff --git a/web/workspace/src/lib/workspace/api/workspace-model.ts b/web/workspace/src/lib/workspace/api/workspace-model.ts index 6449d53b3..e7e7af8e8 100644 --- a/web/workspace/src/lib/workspace/api/workspace-model.ts +++ b/web/workspace/src/lib/workspace/api/workspace-model.ts @@ -1,10 +1,5 @@ import type { ApiResult } from "$lib/workspace/api/http"; import type { - GitCommitSummary, - RepositoryLogResponse, - RepositorySshConnectionProbeResponse, - RepositorySshConnectionTrustState, - RepositorySshHostKeyCandidate, WorkspaceAuthConfig, WorkspaceCatalogListResponse, WorkspaceCreateResponse, @@ -24,22 +19,25 @@ import type { import type { CreateWorkspaceRepositoryResponse, Diagnostic, + GitCommitSummary, GitRemoteSummary, GitRepositorySummary, + HostListResponse, + HostSummary, RepositoryApiError, RepositoryDetailResponse, RepositoryDiagnostic, RepositoryListResponse, + RepositoryLogResponse, RepositorySource, RepositorySourceKind, + RepositorySshConnectionProbeResponse, + RepositorySshConnectionTrustState, + RepositorySshHostKeyCandidate, RepositorySummary, } from "$lib/generated/repository-api.ts"; export type { - GitCommitSummary, - RepositoryLogResponse, - RepositorySshConnectionProbeResponse, - RepositorySshHostKeyCandidate, WorkspaceCatalogListResponse, WorkspaceCreateResponse, WorkspaceDeletionOperationResponse, @@ -51,13 +49,19 @@ export type { export type { CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse, + GitCommitSummary, GitRemoteSummary, GitRepositorySummary, + HostListResponse, + HostSummary, RepositoryApiError, RepositoryDetailResponse, RepositoryListResponse, + RepositoryLogResponse, RepositorySource, RepositorySourceKind, + RepositorySshConnectionProbeResponse, + RepositorySshHostKeyCandidate, RepositorySummary, } from "$lib/generated/repository-api.ts"; @@ -1094,6 +1098,77 @@ export function parseWorkspaceDeletionOperationResponse( }; } +export function parseHostListResponse(value: unknown): HostListResponse { + const response = object(value, "host list response"); + exactKeys( + response, + ["workspace_id", "limit", "items", "source", "diagnostics"], + "host list response", + ); + const limit = repositorySourceRevision( + response.limit, + "host list response.limit", + ); + return { + workspace_id: repositoryString( + response.workspace_id, + "host list response.workspace_id", + ), + limit, + items: repositoryArray(response.items, "host list response.items").map( + (item, index) => parseHostSummary(item, `host list response.items[${index}]`), + ), + source: repositoryString(response.source, "host list response.source"), + diagnostics: repositoryArray( + response.diagnostics, + "host list response.diagnostics", + ).map((item, index) => + repositoryApiDiagnostic( + item, + `host list response.diagnostics[${index}]`, + ) + ), + }; +} + +function parseHostSummary(value: unknown, path: string): HostSummary { + const host = object(value, path); + exactKeys( + host, + [ + "runtime_id", + "host_id", + "label", + "kind", + "status", + "observed_at", + "last_seen_at", + "os", + "arch", + "diagnostics", + ], + path, + ); + return { + runtime_id: repositoryString(host.runtime_id, `${path}.runtime_id`), + host_id: repositoryString(host.host_id, `${path}.host_id`), + label: repositoryString(host.label, `${path}.label`), + kind: repositoryString(host.kind, `${path}.kind`), + status: repositoryString(host.status, `${path}.status`), + observed_at: repositoryString(host.observed_at, `${path}.observed_at`), + last_seen_at: optionalNullableRepositoryString( + host.last_seen_at, + `${path}.last_seen_at`, + ), + os: repositoryString(host.os, `${path}.os`), + arch: repositoryString(host.arch, `${path}.arch`), + diagnostics: repositoryArray(host.diagnostics, `${path}.diagnostics`).map( + (item, index) => + repositoryApiDiagnostic(item, `${path}.diagnostics[${index}]`), + ), + }; +} + export function parseRepositoryLogResponse( value: unknown, ): RepositoryLogResponse { diff --git a/web/workspace/src/lib/workspace/sidebar/types.ts b/web/workspace/src/lib/workspace/sidebar/types.ts index b5734b243..bf33a3742 100644 --- a/web/workspace/src/lib/workspace/sidebar/types.ts +++ b/web/workspace/src/lib/workspace/sidebar/types.ts @@ -71,19 +71,6 @@ export type Runtime = { }; }; -export type Host = { - runtime_id: string; - host_id: string; - label: string; - kind: string; - status: string; - observed_at: string; - last_seen_at: string | null; - os: string; - arch: string; - diagnostics: Diagnostic[]; -}; - export type WorkerCapabilities = { can_stop: boolean; can_spawn_followup: boolean; diff --git a/web/workspace/src/routes/w/[workspaceId]/+page.ts b/web/workspace/src/routes/w/[workspaceId]/+page.ts index 7ebfb3374..daf3fc39f 100644 --- a/web/workspace/src/routes/w/[workspaceId]/+page.ts +++ b/web/workspace/src/routes/w/[workspaceId]/+page.ts @@ -1,10 +1,24 @@ import { loadJson, workspaceApiPath } from "$lib/workspace/api/http"; -import type { Host, ListResponse } from "$lib/workspace/sidebar/types"; +import { + parseHostListResponse, + type HostListResponse, +} from "$lib/workspace/api/workspace-model"; import type { PageLoad } from "./$types"; +const HOST_API_LOAD_POLICY = { + diagnosticLabel: "Host API", + maxResponseBytes: 4 * 1024 * 1024, +} as const; + export const load: PageLoad = async ({ fetch, params }) => { const apiPath = (path: string) => workspaceApiPath(params.workspaceId, path); - const hosts = await loadJson>(fetch, apiPath("/hosts")); + const hosts = await loadJson( + fetch, + apiPath("/hosts"), + undefined, + parseHostListResponse, + HOST_API_LOAD_POLICY, + ); return { workspaceId: params.workspaceId, diff --git a/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.svelte index 5d232c73b..7b8a9d7bc 100644 --- a/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.svelte @@ -3,7 +3,7 @@ ConfirmRepositorySshHostTrustRequest, RepositorySshConnectionProbeRequest, RepositorySshConnectionProbeResponse - } from '$lib/generated/legacy-server-api'; + } from '$lib/generated/repository-api'; import { parseRepositorySshHostTrust } from '$lib/workspace/api/repository-access'; import { formatDate, workspaceApiPath } from '$lib/workspace/api/http'; import { parseRepositorySshConnectionProbeResponse } from '$lib/workspace/api/workspace-model'; From ad12d77ad4469c468465ae1b4095b9abec22b569 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 08:24:39 +0900 Subject: [PATCH 05/16] feat: migrate runtime worker domains to ServerApi --- crates/client/src/backend_runtime.rs | 332 +- crates/client/src/backend_workspace.rs | 27 + crates/client/src/workspace_product.rs | 47 +- .../examples/generate_runtime_api_types.rs | 59 + crates/server-api/src/lib.rs | 1256 ++- .../src/repository_openapi_typescript.rs | 458 ++ crates/workspace-server/src/server.rs | 1044 ++- openapi/server-api.json | 6808 ++++++++++++++++- .../src/lib/generated/legacy-server-api.ts | 204 - .../src/lib/generated/repository-api.ts | 2 +- .../src/lib/generated/runtime-api.ts | 172 + .../src/lib/generated/workdir-api.ts | 2 +- .../lib/workspace/api/runtime-connection.ts | 2 +- .../lib/workspace/api/runtime-management.ts | 23 +- .../src/lib/workspace/api/runtime-workers.ts | 356 + .../workspace/console/composer-attachments.ts | 18 +- .../workspace/repositories/ssh-connection.ts | 2 +- .../src/lib/workspace/sidebar/types.ts | 74 +- .../lib/workspace/sidebar/worker-actions.ts | 19 +- .../settings/runtimes/+page.svelte | 12 +- .../runtimes/[runtimeId]/+page.svelte | 6 +- .../[runtimeId]/workdirs/+page.svelte | 13 +- .../runtimes/[runtimeId]/workdirs/+page.ts | 15 +- .../w/[workspaceId]/workers/+page.svelte | 26 +- .../routes/w/[workspaceId]/workers/+page.ts | 5 +- .../test/repository-ssh-connection-ui.test.ts | 2 +- .../test/sidebar/worker-actions.test.ts | 59 +- web/workspace/tests/runtime-workers.test.ts | 122 + 28 files changed, 10269 insertions(+), 896 deletions(-) create mode 100644 crates/server-api/examples/generate_runtime_api_types.rs create mode 100644 web/workspace/src/lib/generated/runtime-api.ts create mode 100644 web/workspace/src/lib/workspace/api/runtime-workers.ts create mode 100644 web/workspace/tests/runtime-workers.test.ts diff --git a/crates/client/src/backend_runtime.rs b/crates/client/src/backend_runtime.rs index 515b27c73..0ab75ab80 100644 --- a/crates/client/src/backend_runtime.rs +++ b/crates/client/src/backend_runtime.rs @@ -88,28 +88,25 @@ impl BackendRuntimeTarget { content: Vec, ) -> Result { let api = BackendApiClient::from_stored_token(&self.base_url)?; + let client = runtime_server_api_client(&self.base_url, &api)?; + let grant = client + .runtime_worker_attachment_upload_grant( + self.workspace_id.clone(), + self.runtime_id.clone(), + self.worker_id.clone(), + server_api::WorkerFileUploadQuery { + file_name: file_name.to_string(), + media_type: media_type.to_string(), + upload_id: Some(upload_id.to_string()), + }, + ) + .await?; let worker_path = format!( "/api/w/{}/runtimes/{}/workers/{}", path_segment_encode(&self.workspace_id), path_segment_encode(&self.runtime_id), path_segment_encode(&self.worker_id), ); - let grant_path = format!( - "{worker_path}/attachment-upload-grants?file_name={}&media_type={}&upload_id={}", - path_segment_encode(file_name), - path_segment_encode(media_type), - path_segment_encode(&upload_id), - ); - let grant_response = api - .request(HttpMethod::POST, &grant_path)? - .send() - .await - .map_err(BackendRuntimeClientError::Http)?; - api.check_status(grant_response.status())?; - let grant = grant_response - .json::() - .await - .map_err(BackendRuntimeClientError::Http)?; let upload_path = format!( "{worker_path}/attachment-uploads/{}", path_segment_encode(&grant.upload_id), @@ -133,19 +130,14 @@ impl BackendRuntimeTarget { upload_id: &str, ) -> Result<(), BackendRuntimeClientError> { let api = BackendApiClient::from_stored_token(&self.base_url)?; - let path = format!( - "/api/w/{}/runtimes/{}/workers/{}/attachment-uploads/{}", - path_segment_encode(&self.workspace_id), - path_segment_encode(&self.runtime_id), - path_segment_encode(&self.worker_id), - path_segment_encode(upload_id), - ); - let response = api - .request(HttpMethod::DELETE, &path)? - .send() - .await - .map_err(BackendRuntimeClientError::Http)?; - api.check_status(response.status())?; + runtime_server_api_client(&self.base_url, &api)? + .runtime_worker_attachment_upload_cancel( + self.workspace_id.clone(), + self.runtime_id.clone(), + self.worker_id.clone(), + upload_id.to_string(), + ) + .await?; Ok(()) } @@ -154,30 +146,18 @@ impl BackendRuntimeTarget { artifact_id: &str, ) -> Result<(), BackendRuntimeClientError> { let api = BackendApiClient::from_stored_token(&self.base_url)?; - let path = format!( - "/api/w/{}/runtimes/{}/workers/{}/attachments/{}", - path_segment_encode(&self.workspace_id), - path_segment_encode(&self.runtime_id), - path_segment_encode(&self.worker_id), - path_segment_encode(artifact_id), - ); - let response = api - .request(HttpMethod::DELETE, &path)? - .send() - .await - .map_err(BackendRuntimeClientError::Http)?; - api.check_status(response.status())?; + runtime_server_api_client(&self.base_url, &api)? + .runtime_worker_attachment_delete( + self.workspace_id.clone(), + self.runtime_id.clone(), + self.worker_id.clone(), + artifact_id.to_string(), + ) + .await?; Ok(()) } } -#[derive(Deserialize)] -struct AttachmentUploadGrantResponse { - upload_id: String, - #[allow(dead_code)] - expires_at_ms: u64, -} - #[derive(Deserialize)] struct UploadedFileResponse { file: protocol::UploadedFileRef, @@ -280,6 +260,7 @@ pub enum BackendRuntimeClientError { InvalidTarget(String), Api(BackendApiClientError), SessionApi(server_api::ServerApiClientError), + ContractApi(server_api::client_support::ClientError), Http(reqwest::Error), Protocol(String), } @@ -290,6 +271,7 @@ impl fmt::Display for BackendRuntimeClientError { Self::InvalidTarget(message) => f.write_str(message), Self::Api(error) => write!(f, "{error}"), Self::SessionApi(error) => write!(f, "{error}"), + Self::ContractApi(error) => write!(f, "{error}"), Self::Http(error) => write!(f, "{error}"), Self::Protocol(message) => f.write_str(message), } @@ -304,6 +286,16 @@ impl From for BackendRuntimeClientError { } } +impl From> + for BackendRuntimeClientError +{ + fn from( + value: server_api::client_support::ClientError, + ) -> Self { + Self::ContractApi(value) + } +} + impl From for BackendRuntimeClientError { fn from(error: BackendApiClientError) -> Self { Self::Api(error) @@ -318,6 +310,34 @@ impl From for BackendRuntimeClientError { const WORKER_SESSION_RESPONSE_LIMIT: usize = 16 * 1024 * 1024; +fn runtime_server_api_client( + base_url: &str, + backend: &BackendApiClient, +) -> Result, BackendRuntimeClientError> { + server_api::ServerApiClient::builder(base_url) + .map_err(|error| BackendRuntimeClientError::Protocol(error.to_string()))? + .client(backend.asynchronous_client()) + .authorizer(StoredBearerAuthorizer { + authorization: backend.authorization_header_value(), + }) + .response_body_limit(WORKER_SESSION_RESPONSE_LIMIT) + .build() + .map_err(|error| BackendRuntimeClientError::Protocol(error.to_string())) +} + +fn server_api_error_is_auth( + error: &server_api::client_support::ClientError, +) -> bool { + matches!( + error, + server_api::client_support::ClientError::Public { status, .. } + if matches!( + *status, + reqwest::StatusCode::UNAUTHORIZED | reqwest::StatusCode::FORBIDDEN + ) + ) +} + #[derive(Clone, Debug)] struct StoredBearerAuthorizer { authorization: String, @@ -351,15 +371,7 @@ async fn observe_backend_worker_session_with_client( target: &BackendRuntimeTarget, backend: &BackendApiClient, ) -> Result { - let client = server_api::ServerApiClient::builder(&target.base_url) - .map_err(|error| BackendRuntimeClientError::Protocol(error.to_string()))? - .authorizer(StoredBearerAuthorizer { - authorization: backend.authorization_header_value(), - }) - .response_body_limit(WORKER_SESSION_RESPONSE_LIMIT) - .build() - .map_err(|error| BackendRuntimeClientError::Protocol(error.to_string()))?; - client + runtime_server_api_client(&target.base_url, backend)? .worker_session( target.workspace_id.clone(), target.runtime_id.clone(), @@ -426,80 +438,50 @@ pub async fn list_backend_workers( ) -> Result, BackendRuntimeClientError> { validate_list_target(target)?; let api = BackendApiClient::from_stored_token(&target.base_url)?; + let client = runtime_server_api_client(&target.base_url, &api)?; + let workspace_id = target + .workspace_id + .as_deref() + .expect("validated Backend Workspace scope"); if let Some(runtime_id) = target.runtime_id.as_deref() { - let path = backend_runtime_workers_path( - target - .workspace_id - .as_deref() - .expect("validated Backend Workspace scope"), - runtime_id, - ); - let response = api.request(HttpMethod::GET, &path)?.send().await?; - api.check_status(response.status())?; - return Ok(response - .json::>() - .await?); + return client + .runtime_worker_list( + workspace_id.to_string(), + runtime_id.to_string(), + server_api::RuntimeWorkersQuery::default(), + ) + .await + .map(runtime_worker_list_response) + .map_err(Into::into); } - let runtime_path = backend_runtimes_path( - target - .workspace_id - .as_deref() - .expect("validated Backend Workspace scope"), - ); - let response = api.request(HttpMethod::GET, &runtime_path)?.send().await?; - api.check_status(response.status())?; - let runtimes = response - .json::>() - .await?; - + let runtimes = client.runtime_list(workspace_id.to_string()).await?; let mut items = Vec::new(); let mut diagnostics = runtimes.diagnostics; - for runtime in runtimes.items { - let path = backend_runtime_workers_path( - target - .workspace_id - .as_deref() - .expect("validated Backend Workspace scope"), - &runtime.runtime_id, - ); - let response = match api.request(HttpMethod::GET, &path)?.send().await { - Ok(response) => response, - Err(error) => { - diagnostics.push(BackendDiagnostic { - code: "runtime_worker_list_failed".to_string(), - severity: BackendDiagnosticSeverity::Error, - message: format!( - "failed to list workers for runtime {}: {error}", - runtime.runtime_id - ), - }); - continue; + for resource in runtimes.items { + let runtime = resource.runtime; + match client + .runtime_worker_list( + workspace_id.to_string(), + runtime.runtime_id.clone(), + server_api::RuntimeWorkersQuery::default(), + ) + .await + { + Ok(response) => { + diagnostics.extend(response.diagnostics); + items.extend(response.items); } - }; - if matches!( - response.status(), - reqwest::StatusCode::UNAUTHORIZED | reqwest::StatusCode::FORBIDDEN - ) { - api.check_status(response.status())?; - } - if !response.status().is_success() { - diagnostics.push(BackendDiagnostic { + Err(error) if server_api_error_is_auth(&error) => return Err(error.into()), + Err(error) => diagnostics.push(BackendDiagnostic { code: "runtime_worker_list_failed".to_string(), severity: BackendDiagnosticSeverity::Error, message: format!( - "failed to list workers for runtime {}: Backend returned HTTP {}", - runtime.runtime_id, - response.status().as_u16() + "failed to list workers for runtime {}: {error}", + runtime.runtime_id ), - }); - continue; + }), } - let response = response - .json::>() - .await?; - diagnostics.extend(response.diagnostics); - items.extend(response.items); } Ok(BackendRuntimeListResponse { @@ -521,21 +503,21 @@ pub async fn list_backend_stopped_workers( )); }; let api = BackendApiClient::from_stored_token(&target.base_url)?; - let path = backend_runtime_workers_path( - target - .workspace_id - .as_deref() - .expect("validated Backend Workspace scope"), - runtime_id, - ); - let response = api - .request(HttpMethod::GET, &format!("{path}?status=stopped"))? - .send() - .await?; - api.check_status(response.status())?; - Ok(response - .json::>() - .await?) + runtime_server_api_client(&target.base_url, &api)? + .runtime_worker_list( + target + .workspace_id + .as_deref() + .expect("validated Backend Workspace scope") + .to_string(), + runtime_id.to_string(), + server_api::RuntimeWorkersQuery { + status: Some(server_api::RuntimeWorkersStatusFilter::Stopped), + }, + ) + .await + .map(runtime_worker_list_response) + .map_err(Into::into) } pub async fn restore_backend_worker( @@ -543,18 +525,18 @@ pub async fn restore_backend_worker( ) -> Result { validate_target(target)?; let api = BackendApiClient::from_stored_token(&target.base_url)?; - let path = backend_runtime_worker_restore_path( - &target.workspace_id, - &target.runtime_id, - &target.worker_id, - ); - let response = api - .request(HttpMethod::POST, &path)? - .json(&serde_json::json!({})) - .send() - .await?; - let response = api.require_success(response).await?; - Ok(response.json::().await?) + runtime_server_api_client(&target.base_url, &api)? + .runtime_worker_restore( + target.workspace_id.clone(), + target.runtime_id.clone(), + target.worker_id.clone(), + server_api::RestoreTicketAssignmentQuery { + ticket_id: None, + assignment_operation_id: None, + }, + ) + .await + .map_err(Into::into) } pub async fn connect_backend_runtime( @@ -599,6 +581,18 @@ fn protocol_connect_error_message( ) } +fn runtime_worker_list_response( + response: server_api::RuntimeWorkerListResponse, +) -> BackendRuntimeListResponse { + BackendRuntimeListResponse { + workspace_id: response.workspace_id, + limit: response.limit, + items: response.items, + source: response.source, + diagnostics: response.diagnostics, + } +} + fn validate_target(target: &BackendRuntimeTarget) -> Result<(), BackendRuntimeClientError> { if target.base_url.trim().is_empty() { return Err(BackendRuntimeClientError::InvalidTarget( @@ -697,31 +691,6 @@ fn backend_workspace_workers_launch_options_path(workspace_id: &str) -> String { ) } -fn backend_runtimes_path(workspace_id: &str) -> String { - format!("/api/w/{}/runtimes", path_segment_encode(workspace_id)) -} - -fn backend_runtime_workers_path(workspace_id: &str, runtime_id: &str) -> String { - format!( - "/api/w/{}/runtimes/{}/workers", - path_segment_encode(workspace_id), - path_segment_encode(runtime_id) - ) -} - -fn backend_runtime_worker_restore_path( - workspace_id: &str, - runtime_id: &str, - worker_id: &str, -) -> String { - format!( - "/api/w/{}/runtimes/{}/workers/{}/restore", - path_segment_encode(workspace_id), - path_segment_encode(runtime_id), - path_segment_encode(worker_id) - ) -} - fn protocol_ws_request( target: &BackendRuntimeTarget, api: &BackendApiClient, @@ -1051,21 +1020,4 @@ mod tests { serde_json::json!(64); assert!(serde_json::from_value::(stale).is_err()); } - - #[test] - fn workers_path_requires_workspace_scope_for_status_queries() { - let path = backend_runtime_workers_path("team main", "runtime/one"); - assert_eq!( - format!("{path}?status=stopped"), - "/api/w/team%20main/runtimes/runtime%2Fone/workers?status=stopped" - ); - } - - #[test] - fn restore_worker_path_requires_workspace_scope() { - assert_eq!( - backend_runtime_worker_restore_path("team main", "runtime/one", "worker one"), - "/api/w/team%20main/runtimes/runtime%2Fone/workers/worker%20one/restore" - ); - } } diff --git a/crates/client/src/backend_workspace.rs b/crates/client/src/backend_workspace.rs index 35536fa23..639e7cc75 100644 --- a/crates/client/src/backend_workspace.rs +++ b/crates/client/src/backend_workspace.rs @@ -31,6 +31,9 @@ pub enum BackendWorkspaceClientError { Api(BackendApiClientError), Http(reqwest::Error), ServerApi(server_api::client_support::ClientError), + RuntimeManagementApi( + server_api::client_support::ClientError, + ), } impl fmt::Display for BackendWorkspaceClientError { @@ -40,6 +43,7 @@ impl fmt::Display for BackendWorkspaceClientError { Self::Api(error) => write!(f, "{error}"), Self::Http(error) => write!(f, "{error}"), Self::ServerApi(error) => write!(f, "{error}"), + Self::RuntimeManagementApi(error) => write!(f, "{error}"), } } } @@ -91,6 +95,29 @@ pub(crate) fn server_client_error( } } +pub(crate) fn runtime_management_client_error( + backend: &BackendApiClient, + error: server_api::client_support::ClientError, +) -> BackendWorkspaceClientError { + let status = match &error { + server_api::client_support::ClientError::Public { status, .. } => Some(*status), + server_api::client_support::ClientError::Failure(_) => None, + }; + match status { + Some(reqwest::StatusCode::UNAUTHORIZED) => { + BackendWorkspaceClientError::Api(BackendApiClientError::Unauthorized { + origin: backend.origin().clone(), + }) + } + Some(reqwest::StatusCode::FORBIDDEN) => { + BackendWorkspaceClientError::Api(BackendApiClientError::Forbidden { + origin: backend.origin().clone(), + }) + } + _ => BackendWorkspaceClientError::RuntimeManagementApi(error), + } +} + #[derive(Clone, Debug)] pub(crate) struct ServerBearerAuthorizer { authorization: String, diff --git a/crates/client/src/workspace_product.rs b/crates/client/src/workspace_product.rs index e561b9a44..3676e0337 100644 --- a/crates/client/src/workspace_product.rs +++ b/crates/client/src/workspace_product.rs @@ -345,24 +345,40 @@ impl BackendWorkspaceProductClient { pub fn list_runtimes( &self, ) -> Result, BackendWorkspaceClientError> { - self.get_json("/runtimes") + let workspace_id = self.workspace_id.clone(); + let response = + self.generated(move |client| async move { client.runtime_list(workspace_id).await })?; + Ok(ListResponse { + workspace_id: response.workspace_id, + limit: response.limit, + items: response.items, + source: response.source, + diagnostics: response.diagnostics, + }) } pub fn runtime_detail( &self, runtime_id: &str, ) -> Result { - self.get_json(&format!("/runtimes/{}", encode_path_segment(runtime_id))) + let workspace_id = self.workspace_id.clone(); + let runtime_id = runtime_id.to_string(); + self.generated(move |client| async move { + client.runtime_detail(workspace_id, runtime_id).await + }) } pub fn reveal_runtime_trust_key( &self, runtime_id: &str, ) -> Result { - self.get_json(&format!( - "/runtimes/{}/trust-key", - encode_path_segment(runtime_id) - )) + let workspace_id = self.workspace_id.clone(); + let runtime_id = runtime_id.to_string(); + self.generated(move |client| async move { + client + .runtime_trust_key_reveal(workspace_id, runtime_id) + .await + }) } pub fn revoke_runtime_trust_key( @@ -370,11 +386,20 @@ impl BackendWorkspaceProductClient { runtime_id: &str, request: &RevokeRuntimeTrustKeyRequest, ) -> Result { - self.send_json( - Method::DELETE, - &format!("/runtimes/{}/trust-key", encode_path_segment(runtime_id)), - Some(request), - ) + let client = crate::backend_workspace::server_api_client(&self.api)?; + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| BackendWorkspaceClientError::InvalidTarget(error.to_string()))?; + runtime + .block_on(client.runtime_trust_key_revoke( + self.workspace_id.clone(), + runtime_id.to_string(), + request.clone(), + )) + .map_err(|error| { + crate::backend_workspace::runtime_management_client_error(&self.api, error) + }) } pub fn memory_document(&self) -> Result { diff --git a/crates/server-api/examples/generate_runtime_api_types.rs b/crates/server-api/examples/generate_runtime_api_types.rs new file mode 100644 index 000000000..69305365a --- /dev/null +++ b/crates/server-api/examples/generate_runtime_api_types.rs @@ -0,0 +1,59 @@ +use std::fs; +use std::path::Path; + +use server_api::repository_openapi_typescript::{ + INPUT_PATH, RUNTIME_OUTPUT_PATH, generate_runtime_typescript, +}; + +fn main() { + if let Err(error) = run() { + eprintln!("{error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let mut check = false; + for argument in std::env::args().skip(1) { + match argument.as_str() { + "--check" => check = true, + "--help" | "-h" => { + println!( + "Generate the bounded Runtime API TypeScript artifact.\n\n\ + Usage: cargo run -q -p server-api --example generate_runtime_api_types -- [--check]" + ); + return Ok(()); + } + other => return Err(format!("unknown argument `{other}`")), + } + } + + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let input_path = root.join(INPUT_PATH); + let output_path = root.join(RUNTIME_OUTPUT_PATH); + let input = fs::read_to_string(&input_path) + .map_err(|error| format!("failed to read {}: {error}", input_path.display()))?; + let generated = generate_runtime_typescript(&input) + .map_err(|error| format!("failed to generate Runtime API types: {error}"))?; + + if check { + let checked_in = fs::read_to_string(&output_path) + .map_err(|error| format!("failed to read {}: {error}", output_path.display()))?; + if checked_in != generated { + return Err(format!( + "{} is stale; regenerate it without --check", + output_path.display() + )); + } + println!("{} is current", output_path.display()); + return Ok(()); + } + + let temporary_path = output_path.with_extension("ts.tmp"); + fs::write(&temporary_path, generated) + .map_err(|error| format!("failed to write {}: {error}", temporary_path.display()))?; + fs::rename(&temporary_path, &output_path) + .map_err(|error| format!("failed to replace {}: {error}", output_path.display()))?; + println!("generated {}", output_path.display()); + Ok(()) +} diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index 6943e3a87..e93c931a3 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -300,6 +300,42 @@ impl_openapi_schema!( TicketStateChangeRequest, TicketSummarySearchQuery, TicketThreadEventRequest, + RuntimeManagementApiError, + RuntimeWorkersQuery, + RuntimeWorkerSpawnRequest, + RuntimeWorkerSpawnResponse, + RuntimeConfigBundleSyncRequest, + RuntimeConfigBundleAvailabilityQuery, + RuntimeConfigBundleResult, + RuntimeWorkerShowResponse, + RuntimeWorkerInputRequest, + RuntimeWorkerInputResult, + RuntimeWorkerLifecycleRequest, + RuntimeWorkerLifecycleResult, + RuntimeWorkerCompletionsRequest, + RuntimeWorkerCompletionsResult, + RestoreTicketAssignmentQuery, + RuntimeCleanupPlanResponse, + ExecuteRuntimeCleanupRequest, + RuntimeCleanupExecutionResponse, + WorkerRetentionResponse, + WorkerFileUploadQuery, + AttachmentUploadGrantResponse, + AttachmentUploadCancelResponse, + WorkerFileDeleteResponse, + CreateRemoteRuntimeRequest, + WorkspaceRuntimeResource, + WorkspaceRuntimeDetail, + UpdateRemoteRuntimeRequest, + RemoveRuntimeRequest, + RuntimeRemovalOperationResponse, + RuntimeTrustKeyRevealResponse, + RevokeRuntimeTrustKeyRequest, + RuntimeConnectionTestResponse, + RuntimeListResponse, + WorkspaceRuntimeListResponse, + RuntimeWorkerListResponse, + WorkerRestoreResponse, ); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -992,6 +1028,547 @@ pub trait ServerApi { #[path] worker_id: String, ) -> Result; + #[get( + "/api/runtimes", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_list_alias(&self) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_list( + &self, + #[path] workspace_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes", + status = 201, + alternate_status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_create( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[body] request: CreateRemoteRuntimeRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_detail( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_update( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: UpdateRemoteRuntimeRequest, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_remove( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: RemoveRuntimeRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_trust_key_reveal( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_trust_key_revoke( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: RevokeRuntimeTrustKeyRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/connection-tests", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_connection_test( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + ) -> Result; + + #[get( + "/api/runtimes/{runtime_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_list_alias( + &self, + #[path] runtime_id: String, + #[query] query: RuntimeWorkersQuery, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_create_alias( + &self, + #[path] runtime_id: String, + #[body] request: RuntimeWorkerSpawnRequest, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_list( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[query] query: RuntimeWorkersQuery, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_create( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: RuntimeWorkerSpawnRequest, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/config-bundles", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_config_bundle_sync_alias( + &self, + #[path] runtime_id: String, + #[body] request: RuntimeConfigBundleSyncRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_config_bundle_sync( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: RuntimeConfigBundleSyncRequest, + ) -> Result; + + #[get( + "/api/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_config_bundle_availability_alias( + &self, + #[path] runtime_id: String, + #[path] bundle_id: String, + #[query] query: RuntimeConfigBundleAvailabilityQuery, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_config_bundle_availability( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] bundle_id: String, + #[query] query: RuntimeConfigBundleAvailabilityQuery, + ) -> Result; + + #[get( + "/api/runtimes/{runtime_id}/workers/{worker_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_detail_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}", + status = 200, + error_status = 404, + additional_error_statuses = [400, 401, 403, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_detail( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers/{worker_id}/restore", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_restore_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/restore", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_restore( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[query] query: RestoreTicketAssignmentQuery, + ) -> Result; + + #[put( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_pin( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_unpin( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + ) -> Result; + + #[get( + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_cleanup_plan( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-executions", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_cleanup_execute( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[body] request: ExecuteRuntimeCleanupRequest, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers/{worker_id}/input", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_input_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerInputRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/input", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_input( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerInputRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-upload-grants", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_attachment_upload_grant( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[query] query: WorkerFileUploadQuery, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-uploads/{upload_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_attachment_upload_cancel( + &self, + #[extension] actor: RequestActor, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[path] upload_id: String, + ) -> Result; + + #[delete( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachments/{artifact_id}", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_attachment_delete( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[path] artifact_id: String, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers/{worker_id}/completions", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_completions_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerCompletionsRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/completions", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_completions( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerCompletionsRequest, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers/{worker_id}/stop", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_stop_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerLifecycleRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/stop", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_stop( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerLifecycleRequest, + ) -> Result; + + #[post( + "/api/runtimes/{runtime_id}/workers/{worker_id}/cancel", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_cancel_alias( + &self, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerLifecycleRequest, + ) -> Result; + + #[post( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/cancel", + status = 200, + error_status = 400, + additional_error_statuses = [401, 403, 404, 409, 500, 502, 503], + bearer_auth = true, + browser_auth = true + )] + async fn runtime_worker_cancel( + &self, + #[path] workspace_id: String, + #[path] runtime_id: String, + #[path] worker_id: String, + #[body] request: RuntimeWorkerLifecycleRequest, + ) -> Result; + #[get( "/api/w/{workspace_id}/repositories", status = 200, @@ -5113,6 +5690,496 @@ pub struct CompleteMergeRequestRequest { pub resolution: ConflictResolution, } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeManagementApiError { + pub error: String, + pub message: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub diagnostics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub current_revision: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub current_fingerprint: Option, + #[serde(skip, default = "default_repository_error_status")] + status: u16, +} + +impl RuntimeManagementApiError { + pub fn trust_conflict(conflict: RuntimeTrustConflictResponse) -> Self { + Self { + error: match conflict.error { + RuntimeTrustConflictKind::StaleRevision => "stale_revision".to_string(), + RuntimeTrustConflictKind::FingerprintInUse => "fingerprint_in_use".to_string(), + }, + message: conflict.message, + diagnostics: Vec::new(), + current_revision: conflict.current_revision, + current_fingerprint: conflict.current_fingerprint, + status: 409, + } + } +} + +impl api_macros::HttpError for RuntimeManagementApiError { + fn status_code(&self) -> u16 { + self.status + } +} + +impl api_macros::HttpRequestError for RuntimeManagementApiError { + fn from_request_rejection(status: u16, message: String) -> Self { + Self::from(RepositoryApiError::from_request_rejection(status, message)) + } +} + +impl From for RuntimeManagementApiError { + fn from(error: RepositoryApiError) -> Self { + Self { + error: error.error, + message: error.message, + diagnostics: error.diagnostics, + current_revision: None, + current_fingerprint: None, + status: error.status, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeListResponse { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub limit: usize, + pub items: Vec, + pub source: String, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkspaceRuntimeListResponse { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub limit: usize, + pub items: Vec, + pub source: String, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerListResponse { + pub workspace_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub limit: usize, + pub items: Vec, + pub source: String, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkersQuery { + pub status: Option, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RuntimeWorkersStatusFilter { + Stopped, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde( + tag = "kind", + content = "value", + rename_all = "snake_case", + deny_unknown_fields +)] +pub enum RuntimeProfileSelector { + Builtin(String), + Named(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] +pub enum RuntimeWorkerSpawnIntent { + WorkspaceCompanion, + WorkspaceOrchestrator, + WorkspaceCoding, + TicketRole { + ticket_id: String, + role: RuntimeTicketWorkerRole, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RuntimeTicketWorkerRole { + Intake, + Orchestrator, + Coder, + Reviewer, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RuntimeWorkerSpawnAcceptanceRequirement { + SocketReady, + RunAccepted { + #[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))] + expected_segments: usize, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerTicketAssignmentRequest { + pub ticket_id: String, + pub operation_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSpawnWorkingDirectoryRequest { + pub repository_key: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub selector: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest { + pub alias: String, + pub working_directory: RuntimeWorkerSpawnWorkingDirectoryRequest, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSpawnRequest { + pub intent: RuntimeWorkerSpawnIntent, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub requested_worker_name: Option, + pub acceptance: RuntimeWorkerSpawnAcceptanceRequirement, + pub profile: RuntimeProfileSelector, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ticket_assignment: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_submit: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub workdir_attachment_requests: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSummary { + pub runtime_id: String, + pub worker_id: String, + pub host_id: String, + #[serde(default)] + pub display_name: String, + pub label: String, + pub profile: Option, + pub singleton_key: Option, + #[serde(default)] + pub tags: Vec, + pub workspace: WorkerWorkspaceSummary, + pub state: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker_state: Option, + pub last_seen_at: Option, + #[serde(default)] + pub pinned: bool, + #[serde(default)] + pub retention_state: String, + pub implementation: WorkerImplementationSummary, + pub capabilities: WorkerCapabilitySummary, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub workdir_attachments: Vec, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerShowResponse { + #[serde(flatten)] + pub worker: WorkerSummary, + pub updated_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSpawnResponse { + pub state: WorkerOperationState, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[serde(default)] + pub acceptance_evidence: Vec, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerSpawnAcceptanceEvidence { + pub kind: String, + pub detail: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeConfigBundleSyncRequest { + pub bundle: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeConfigBundleAvailabilityQuery { + pub digest: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeConfigBundleResult { + pub state: WorkerOperationState, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub availability: Option, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerInputRequest { + #[serde(default)] + pub kind: Option, + pub content: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub segments: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerInputResult { + pub state: WorkerOperationState, + pub runtime_id: String, + pub worker_id: String, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerLifecycleRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ticket_assignment: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerLifecycleResult { + pub state: WorkerOperationState, + pub runtime_id: String, + pub worker_id: String, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerCompletionsRequest { + pub kind: serde_json::Value, + #[serde(default)] + pub prefix: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeWorkerCompletionsResult { + pub runtime_id: String, + pub worker_id: String, + pub kind: serde_json::Value, + pub prefix: String, + pub entries: Vec, + #[serde(default)] + pub diagnostics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RestoreTicketAssignmentQuery { + pub ticket_id: Option, + pub assignment_operation_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CleanupTargetKind { + WorkerDelete, + WorkdirCleanCleanup, + WorkdirDirtyDiscard, + WorkdirRecordDelete, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CleanupWorkdirFileStatus { + Pending, + Present, + Active, + CleanupPending, + NotFound, + Corrupted, + Failed, + Unknown, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CleanupWorkdirCleanliness { + Clean, + Dirty, + Unknown, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +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, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub estimated_reclaim_bytes: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CleanupWorkdirCandidate { + pub target_id: String, + pub action: CleanupTargetKind, + pub workdir_id: String, + pub runtime_id: String, + pub repository_key: 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, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub estimated_reclaim_bytes: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +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, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[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, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct RuntimeCleanupExecutionResult { + pub target_id: String, + pub action: CleanupTargetKind, + pub status: String, + pub message: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +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, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkerRetentionResponse { + pub workspace_id: String, + pub runtime_id: String, + pub worker_id: String, + pub pinned: bool, + pub retention_state: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkerFileUploadQuery { + pub file_name: String, + pub media_type: String, + #[serde(default)] + pub upload_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct WorkerFileUploadResponse { + pub file: protocol::UploadedFileRef, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct WorkerFileDeleteResponse { + pub deleted: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct AttachmentUploadGrantResponse { + pub upload_id: String, + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] + pub expires_at_ms: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct AttachmentUploadCancelResponse { + pub cancelled: bool, +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] @@ -5121,7 +6188,7 @@ pub enum RuntimeSourceKind { RemoteHttp, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeSourceStatus { @@ -5129,7 +6196,7 @@ pub enum RuntimeSourceStatus { Reserved, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeIdentityAuthority { @@ -5137,7 +6204,7 @@ pub enum RuntimeIdentityAuthority { ServerRuntimeConfiguration, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeSourceSummary { @@ -5147,7 +6214,7 @@ pub struct RuntimeSourceSummary { pub note: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct RuntimeSummary { pub runtime_id: String, @@ -5164,7 +6231,7 @@ pub struct RuntimeSummary { pub diagnostics: Vec, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkspaceRuntimeBindingState { @@ -5173,7 +6240,7 @@ pub enum WorkspaceRuntimeBindingState { Revoked, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeConnectionDisplayState { @@ -5183,7 +6250,7 @@ pub enum RuntimeConnectionDisplayState { Revoked, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeVerificationOutcome { @@ -5193,7 +6260,7 @@ pub enum RuntimeVerificationOutcome { ConnectivityFailed, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeVerificationEvidenceSummary { @@ -5201,35 +6268,41 @@ pub struct RuntimeVerificationEvidenceSummary { pub last_checked_at: String, pub last_outcome: RuntimeVerificationOutcome, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub binding_revision: u64, pub workspace_key_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub workspace_identity_revision: u64, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub workspace_trust_generation: u64, pub runtime_public_key_fingerprint: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub runtime_identity_revision: u64, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceRuntimeBindingSummary { pub state: WorkspaceRuntimeBindingState, pub connection_state: RuntimeConnectionDisplayState, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub revision: u64, #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace_key_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub workspace_key_generation: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub verification: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeManagementSummary { @@ -5242,15 +6315,29 @@ pub struct RuntimeManagementSummary { pub binding: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] pub struct WorkspaceRuntimeResource { #[serde(flatten)] pub runtime: RuntimeSummary, pub management: RuntimeManagementSummary, + #[serde(skip, default = "default_runtime_mutation_status")] + #[cfg_attr(feature = "typescript", ts(skip))] + #[schemars(skip)] + pub http_status: u16, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +const fn default_runtime_mutation_status() -> u16 { + 200 +} + +impl api_macros::HttpSuccess for WorkspaceRuntimeResource { + fn status_code(&self) -> u16 { + self.http_status + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeTrustKeyStatus { @@ -5259,7 +6346,7 @@ pub enum RuntimeTrustKeyStatus { Revoked, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeTrustKeyState { @@ -5268,6 +6355,7 @@ pub struct RuntimeTrustKeyState { pub fingerprint: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub revision: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub created_at: Option, @@ -5277,7 +6365,7 @@ pub struct RuntimeTrustKeyState { pub revoked_at: Option, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeTrustAuditAction { @@ -5287,7 +6375,7 @@ pub enum RuntimeTrustAuditAction { Revoked, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeTrustAuditEntry { @@ -5298,11 +6386,12 @@ pub struct RuntimeTrustAuditEntry { #[serde(default, skip_serializing_if = "Option::is_none")] pub new_fingerprint: Option, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub revision: u64, pub at: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkspaceRuntimeDetail { @@ -5315,31 +6404,33 @@ pub struct WorkspaceRuntimeDetail { pub recent_audit: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeTrustKeyRevealResponse { pub public_key: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RevokeRuntimeTrustKeyRequest { #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: u64, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RemoveRuntimeRequest { pub operation_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_binding_revision: u64, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeRemovalOperationState { @@ -5349,7 +6440,7 @@ pub enum RuntimeRemovalOperationState { Failed, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeRemovalOperationResponse { @@ -5367,7 +6458,7 @@ pub struct RuntimeRemovalOperationResponse { pub completed_at: Option, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeTrustConflictKind { @@ -5375,7 +6466,7 @@ pub enum RuntimeTrustConflictKind { FingerprintInUse, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeTrustConflictResponse { @@ -5383,12 +6474,13 @@ pub struct RuntimeTrustConflictResponse { pub message: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub current_revision: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub current_fingerprint: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimePublicIdentityBundle { @@ -5396,7 +6488,7 @@ pub struct RuntimePublicIdentityBundle { pub public_key: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct CreateRemoteRuntimeRequest { @@ -5406,10 +6498,11 @@ pub struct CreateRemoteRuntimeRequest { pub endpoint: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(type = "number | null"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub expected_revision: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct UpdateRemoteRuntimeRequest { @@ -5418,7 +6511,7 @@ pub struct UpdateRemoteRuntimeRequest { pub endpoint: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeConnectionTestStatus { @@ -5426,7 +6519,7 @@ pub enum RuntimeConnectionTestStatus { Failed, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum RuntimeConnectionTestFailureKind { @@ -5441,20 +6534,23 @@ pub enum RuntimeConnectionTestFailureKind { Configuration, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct RuntimeConnectionTestResponse { pub workspace_id: String, pub runtime_id: String, #[cfg_attr(feature = "typescript", ts(type = "number"))] + #[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))] pub binding_revision: u64, pub connection_state: RuntimeConnectionDisplayState, pub verification: Option, pub checked_at: String, pub status: RuntimeConnectionTestStatus, pub failure_kind: Option, + #[schemars(range(min = 0, max = 4_294_967_295_u32))] pub expected_protocol_version: u32, + #[schemars(range(min = 0, max = 4_294_967_295_u32))] pub actual_protocol_version: Option, #[serde(default)] pub diagnostics: Vec, @@ -5840,7 +6936,7 @@ pub struct BrowserWorkspaceOrchestratorResponse { pub diagnostics: Vec, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkerOperationState { Accepted, @@ -5853,7 +6949,7 @@ pub enum WorkerOperationState { /// `Rejected` is reserved for read-only preflight failures. Once live restore /// work starts, failure is either a confirmed `RolledBack` operation or a /// `ReconciliationRequired` result whose commit state must be reread/retried. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(rename_all = "snake_case")] pub enum WorkerRestoreState { @@ -5863,7 +6959,7 @@ pub enum WorkerRestoreState { ReconciliationRequired, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkerRestoreResult { @@ -5875,7 +6971,7 @@ pub struct WorkerRestoreResult { pub diagnostics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[serde(deny_unknown_fields)] pub struct WorkerRestoreResponse { @@ -6772,36 +7868,6 @@ pub fn legacy_catalog_typescript() -> String { RepositorySshConnectionProbeResponse::decl(&config), ConfirmRepositorySshHostTrustRequest::decl(&config), RepositoryLogResponse::decl(&config), - RuntimeSourceKind::decl(&config), - RuntimeSourceStatus::decl(&config), - RuntimeIdentityAuthority::decl(&config), - RuntimeSourceSummary::decl(&config), - RuntimeSummary::decl(&config), - WorkspaceRuntimeBindingState::decl(&config), - RuntimeConnectionDisplayState::decl(&config), - RuntimeVerificationOutcome::decl(&config), - RuntimeVerificationEvidenceSummary::decl(&config), - WorkspaceRuntimeBindingSummary::decl(&config), - RuntimeManagementSummary::decl(&config), - WorkspaceRuntimeResource::decl(&config), - RuntimeTrustKeyStatus::decl(&config), - RuntimeTrustKeyState::decl(&config), - RuntimeTrustAuditAction::decl(&config), - RuntimeTrustAuditEntry::decl(&config), - WorkspaceRuntimeDetail::decl(&config), - RuntimeTrustKeyRevealResponse::decl(&config), - RevokeRuntimeTrustKeyRequest::decl(&config), - RemoveRuntimeRequest::decl(&config), - RuntimeRemovalOperationState::decl(&config), - RuntimeRemovalOperationResponse::decl(&config), - RuntimeTrustConflictKind::decl(&config), - RuntimeTrustConflictResponse::decl(&config), - RuntimePublicIdentityBundle::decl(&config), - CreateRemoteRuntimeRequest::decl(&config), - UpdateRemoteRuntimeRequest::decl(&config), - RuntimeConnectionTestStatus::decl(&config), - RuntimeConnectionTestFailureKind::decl(&config), - RuntimeConnectionTestResponse::decl(&config), ] .map(|declaration| format!("export {declaration}")); @@ -6913,35 +7979,6 @@ pub fn auth_api_typescript() -> String { ) } -#[cfg(feature = "typescript")] -pub fn workdir_api_typescript() -> String { - use ts_rs::TS; - - let config = ts_rs::Config::default(); - let declarations = [ - DiagnosticSeverity::decl(&config), - Diagnostic::decl(&config), - WorkingDirectoryMaterializerKind::decl(&config), - WorkingDirectoryStatusKind::decl(&config), - WorkingDirectoryCleanupTarget::decl(&config), - WorkingDirectoryOccupancy::decl(&config), - WorkingDirectorySource::decl(&config), - WorkingDirectorySummary::decl(&config), - WorkingDirectoryCreateRequest::decl(&config), - WorkingDirectoryListResponse::decl(&config), - WorkingDirectoryDetailResponse::decl(&config), - WorkingDirectoryCreateResponse::decl(&config), - ]; - format!( - "// Generated from server-api. Do not edit by hand.\n// Regenerate: cargo run -q -p server-api --features typescript --example generate_workdir_api_types > web/workspace/src/lib/generated/workdir-api.ts\n\n{}\n", - declarations - .into_iter() - .map(|declaration| format!("export {declaration}")) - .collect::>() - .join("\n\n") - ) -} - #[cfg(feature = "typescript")] pub fn worker_launch_api_typescript() -> String { use ts_rs::TS; @@ -7072,35 +8109,6 @@ mod skill_typescript_tests { } } -#[cfg(all(test, feature = "typescript"))] -mod workdir_typescript_tests { - #[test] - fn generated_workdir_api_contract_is_current() { - let expected = super::workdir_api_typescript(); - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../../web/workspace/src/lib/generated/workdir-api.ts"); - let actual = std::fs::read_to_string(&path) - .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())); - assert_eq!( - normalize(&actual), - normalize(&expected), - "regenerate Workdir API TypeScript types with `cargo run -q -p server-api --features typescript --example generate_workdir_api_types > web/workspace/src/lib/generated/workdir-api.ts` and format the generated file", - ); - } - - fn normalize(value: &str) -> String { - value - .chars() - .filter_map(|character| match character { - character if character.is_whitespace() => None, - ',' => Some(';'), - character => Some(character), - }) - .collect::() - .replace("=|", "=") - } -} - #[cfg(test)] mod tests { use super::*; @@ -8139,9 +9147,9 @@ mod tests { assert!(output.contains( "export type WorkspaceProfileSourceProvenance = \"project_profile_source_tree\"" )); - assert!(output.contains("export type RuntimeConnectionTestResponse =")); - assert!(output.contains("status: RuntimeConnectionTestStatus")); - assert!(output.contains("failure_kind: RuntimeConnectionTestFailureKind | null")); + assert!(!output.contains("export type RuntimeConnectionTestResponse =")); + assert!(!output.contains("export type RuntimePublicIdentityBundle =")); + assert!(!output.contains("export type WorkspaceRuntimeResource =")); assert!(!output.contains("repository_key: string, display_name")); } diff --git a/crates/server-api/src/repository_openapi_typescript.rs b/crates/server-api/src/repository_openapi_typescript.rs index fa374443f..58d62758a 100644 --- a/crates/server-api/src/repository_openapi_typescript.rs +++ b/crates/server-api/src/repository_openapi_typescript.rs @@ -12,6 +12,11 @@ pub const WORKDIR_GENERATOR_VERSION: &str = "1"; pub const WORKDIR_GENERATOR_OPTIONS: &str = "workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output"; pub const WORKDIR_OUTPUT_PATH: &str = "web/workspace/src/lib/generated/workdir-api.ts"; +pub const RUNTIME_GENERATOR_NAME: &str = "yoi-runtime-openapi-typescript"; +pub const RUNTIME_GENERATOR_VERSION: &str = "1"; +pub const RUNTIME_GENERATOR_OPTIONS: &str = + "runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output"; +pub const RUNTIME_OUTPUT_PATH: &str = "web/workspace/src/lib/generated/runtime-api.ts"; const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0; const MIN_SAFE_INTEGER: f64 = -9_007_199_254_740_991.0; @@ -119,6 +124,48 @@ pub fn generate_workdir_typescript(input: &str) -> Result Result { + let document: Value = serde_json::from_str(input) + .map_err(|error| GenerationError::invalid(format!("invalid OpenAPI JSON: {error}")))?; + let root = object(&document, "OpenAPI document")?; + if root.get("openapi").and_then(Value::as_str) != Some("3.1.0") { + return Err(GenerationError::invalid( + "Runtime TypeScript generation requires OpenAPI 3.1.0", + )); + } + + let info = object_field(root, "info", "OpenAPI document")?; + let contract_version = required_string(info, "version", "OpenAPI info")?; + let contract_digest = required_string(info, "x-yoi-source-digest", "OpenAPI info")?; + validate_contract_digest(contract_digest)?; + let schemas = object_field( + object_field(root, "components", "OpenAPI document")?, + "schemas", + "OpenAPI components", + )?; + let paths = object_field(root, "paths", "OpenAPI document")?; + let roots = validate_runtime_operations(paths)?; + let closure = schema_closure(schemas, &roots)?; + + let mut output = format!( + "// @generated by {RUNTIME_GENERATOR_NAME}@{RUNTIME_GENERATOR_VERSION}. Do not edit.\n\ +// Input: {INPUT_PATH}\n\ +// Input contract: {contract_version} ({contract_digest})\n\ +// Options: {RUNTIME_GENERATOR_OPTIONS}\n\ +// Output: {RUNTIME_OUTPUT_PATH}\n\ +// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check\n\n" + ); + for name in closure { + let schema = schemas + .get(&name) + .ok_or_else(|| GenerationError::invalid(format!("missing schema `{name}`")))?; + let declaration = render_schema(schema, &format!("schema `{name}`"))?; + output.push_str(&format!("export type {name} = {declaration};\n\n")); + } + output.pop(); + Ok(output) +} + fn validate_contract_digest(digest: &str) -> Result<(), GenerationError> { let Some(hex) = digest.strip_prefix("sha256:") else { return Err(GenerationError::invalid( @@ -347,6 +394,364 @@ fn validate_workdir_operations( validate_operation_contracts(paths, &operations) } +fn validate_runtime_operations( + paths: &Map, +) -> Result, GenerationError> { + let operations = [ + runtime_operation( + "/api/runtimes", + "get", + "runtime_list_alias", + None, + &[("200", "RuntimeListResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes", + "get", + "runtime_list", + None, + &[("200", "WorkspaceRuntimeListResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes", + "post", + "runtime_create", + Some("CreateRemoteRuntimeRequest"), + &[ + ("200", "WorkspaceRuntimeResource"), + ("201", "WorkspaceRuntimeResource"), + ], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + "get", + "runtime_detail", + None, + &[("200", "WorkspaceRuntimeDetail")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + "post", + "runtime_update", + Some("UpdateRemoteRuntimeRequest"), + &[("200", "WorkspaceRuntimeDetail")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}", + "delete", + "runtime_remove", + Some("RemoveRuntimeRequest"), + &[("200", "RuntimeRemovalOperationResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key", + "get", + "runtime_trust_key_reveal", + None, + &[("200", "RuntimeTrustKeyRevealResponse")], + ), + RuntimeOperationContract { + error: "RuntimeManagementApiError", + ..runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key", + "delete", + "runtime_trust_key_revoke", + Some("RevokeRuntimeTrustKeyRequest"), + &[("200", "WorkspaceRuntimeDetail")], + ) + }, + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/connection-tests", + "post", + "runtime_connection_test", + None, + &[("200", "RuntimeConnectionTestResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers", + "get", + "runtime_worker_list_alias", + None, + &[("200", "RuntimeWorkerListResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers", + "post", + "runtime_worker_create_alias", + Some("RuntimeWorkerSpawnRequest"), + &[("200", "RuntimeWorkerSpawnResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers", + "get", + "runtime_worker_list", + None, + &[("200", "RuntimeWorkerListResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers", + "post", + "runtime_worker_create", + Some("RuntimeWorkerSpawnRequest"), + &[("200", "RuntimeWorkerSpawnResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/config-bundles", + "post", + "runtime_config_bundle_sync_alias", + Some("RuntimeConfigBundleSyncRequest"), + &[("200", "RuntimeConfigBundleResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles", + "post", + "runtime_config_bundle_sync", + Some("RuntimeConfigBundleSyncRequest"), + &[("200", "RuntimeConfigBundleResult")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", + "get", + "runtime_config_bundle_availability_alias", + None, + &[("200", "RuntimeConfigBundleResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", + "get", + "runtime_config_bundle_availability", + None, + &[("200", "RuntimeConfigBundleResult")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}", + "get", + "runtime_worker_detail_alias", + None, + &[("200", "RuntimeWorkerShowResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}", + "get", + "runtime_worker_detail", + None, + &[("200", "RuntimeWorkerShowResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}/restore", + "post", + "runtime_worker_restore_alias", + None, + &[("200", "WorkerRestoreResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/restore", + "post", + "runtime_worker_restore", + None, + &[("200", "WorkerRestoreResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin", + "put", + "runtime_worker_pin", + None, + &[("200", "WorkerRetentionResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin", + "delete", + "runtime_worker_unpin", + None, + &[("200", "WorkerRetentionResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan", + "get", + "runtime_cleanup_plan", + None, + &[("200", "RuntimeCleanupPlanResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-executions", + "post", + "runtime_cleanup_execute", + Some("ExecuteRuntimeCleanupRequest"), + &[("200", "RuntimeCleanupExecutionResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}/input", + "post", + "runtime_worker_input_alias", + Some("RuntimeWorkerInputRequest"), + &[("200", "RuntimeWorkerInputResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/input", + "post", + "runtime_worker_input", + Some("RuntimeWorkerInputRequest"), + &[("200", "RuntimeWorkerInputResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-upload-grants", + "post", + "runtime_worker_attachment_upload_grant", + None, + &[("200", "AttachmentUploadGrantResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-uploads/{upload_id}", + "delete", + "runtime_worker_attachment_upload_cancel", + None, + &[("200", "AttachmentUploadCancelResponse")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachments/{artifact_id}", + "delete", + "runtime_worker_attachment_delete", + None, + &[("200", "WorkerFileDeleteResponse")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}/completions", + "post", + "runtime_worker_completions_alias", + Some("RuntimeWorkerCompletionsRequest"), + &[("200", "RuntimeWorkerCompletionsResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/completions", + "post", + "runtime_worker_completions", + Some("RuntimeWorkerCompletionsRequest"), + &[("200", "RuntimeWorkerCompletionsResult")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}/stop", + "post", + "runtime_worker_stop_alias", + Some("RuntimeWorkerLifecycleRequest"), + &[("200", "RuntimeWorkerLifecycleResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/stop", + "post", + "runtime_worker_stop", + Some("RuntimeWorkerLifecycleRequest"), + &[("200", "RuntimeWorkerLifecycleResult")], + ), + runtime_operation( + "/api/runtimes/{runtime_id}/workers/{worker_id}/cancel", + "post", + "runtime_worker_cancel_alias", + Some("RuntimeWorkerLifecycleRequest"), + &[("200", "RuntimeWorkerLifecycleResult")], + ), + runtime_operation( + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/cancel", + "post", + "runtime_worker_cancel", + Some("RuntimeWorkerLifecycleRequest"), + &[("200", "RuntimeWorkerLifecycleResult")], + ), + ]; + validate_runtime_operation_contracts(paths, &operations) +} + +const fn runtime_operation( + path: &'static str, + method: &'static str, + operation_id: &'static str, + request: Option<&'static str>, + successes: &'static [(&'static str, &'static str)], +) -> RuntimeOperationContract { + RuntimeOperationContract { + path, + method, + operation_id, + request, + successes, + error: "RepositoryApiError", + } +} + +fn validate_runtime_operation_contracts( + paths: &Map, + operations: &[RuntimeOperationContract], +) -> Result, GenerationError> { + let mut roots = BTreeSet::new(); + for operation in operations { + let path = object_field(paths, operation.path, "OpenAPI paths")?; + let value = object_field(path, operation.method, operation.path)?; + if value.get("operationId").and_then(Value::as_str) != Some(operation.operation_id) { + return Err(GenerationError::invalid(format!( + "{} {} must retain operationId `{}`", + operation.method, operation.path, operation.operation_id + ))); + } + match operation.request { + Some(expected) => { + let request = object_field(value, "requestBody", operation.operation_id)?; + require_schema_ref( + json_content_schema(request, operation.operation_id)?, + expected, + operation.operation_id, + )?; + roots.insert(expected.to_owned()); + } + None if value.contains_key("requestBody") => { + return Err(GenerationError::invalid(format!( + "{} unexpectedly gained a request body", + operation.operation_id + ))); + } + None => {} + } + let responses = object_field(value, "responses", operation.operation_id)?; + let successes = operation + .successes + .iter() + .copied() + .collect::>(); + for (status, response) in responses { + let response = object( + response, + &format!("{} response {status}", operation.operation_id), + )?; + let expected = successes + .get(status.as_str()) + .copied() + .unwrap_or(operation.error); + require_schema_ref( + json_content_schema(response, operation.operation_id)?, + expected, + operation.operation_id, + )?; + roots.insert(expected.to_owned()); + } + for (status, expected) in successes { + if !responses.contains_key(status) { + return Err(GenerationError::invalid(format!( + "{} is missing success response {status} ({expected})", + operation.operation_id + ))); + } + } + } + Ok(roots) +} + +struct RuntimeOperationContract { + path: &'static str, + method: &'static str, + operation_id: &'static str, + request: Option<&'static str>, + successes: &'static [(&'static str, &'static str)], + error: &'static str, +} + fn validate_operation_contracts( paths: &Map, operations: &[OperationContract], @@ -686,6 +1091,21 @@ fn render_schema(value: &Value, context: &str) -> Result>>(); + if let Some(types) = disjoint_types { + let unique = types.iter().copied().collect::>(); + if outer_type.is_none() && unique.len() == branches.len() { + return branches + .iter() + .map(|branch| render_schema(branch, context)) + .collect::, _>>() + .map(|branches| branches.join(" | ")); + } + } + let mut rendered = Vec::with_capacity(branches.len()); let mut seen_literals = BTreeSet::new(); for branch in branches { @@ -1007,6 +1427,44 @@ mod tests { const OPENAPI: &str = include_str!("../../../openapi/server-api.json"); const TYPESCRIPT: &str = include_str!("../../../web/workspace/src/lib/generated/repository-api.ts"); + const RUNTIME_TYPESCRIPT: &str = + include_str!("../../../web/workspace/src/lib/generated/runtime-api.ts"); + + #[test] + fn checked_in_runtime_typescript_is_current() { + assert_eq!( + generate_runtime_typescript(OPENAPI).unwrap(), + RUNTIME_TYPESCRIPT, + "regenerate with `cargo run -q -p server-api --example generate_runtime_api_types`", + ); + } + + #[test] + fn runtime_generator_validates_operation_ownership_and_schema_closure() { + let output = generate_runtime_typescript(OPENAPI).unwrap(); + assert!(output.contains(&format!( + "@generated by {RUNTIME_GENERATOR_NAME}@{RUNTIME_GENERATOR_VERSION}" + ))); + assert!(output.contains("export type RuntimeCleanupPlanResponse =")); + assert!(output.contains("export type RuntimeWorkerLifecycleResult =")); + assert!(output.contains("export type RuntimeWorkerSpawnAcceptanceRequirement = \"socket_ready\" | { run_accepted:")); + assert!(!output.contains("export type UnmigratedDomain =")); + + let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); + document["paths"]["/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan"]["get"]["operationId"] = + serde_json::json!("manual_cleanup_plan"); + let error = generate_runtime_typescript(&document.to_string()).unwrap_err(); + assert!(error.to_string().contains("runtime_cleanup_plan")); + + let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); + document["components"]["schemas"]["RuntimeWorkerSpawnAcceptanceRequirement"] + ["oneOf"] + .as_array_mut() + .unwrap() + .push(serde_json::json!({"type": "object", "properties": {}, "additionalProperties": false})); + let error = generate_runtime_typescript(&document.to_string()).unwrap_err(); + assert!(error.to_string().contains("ambiguous oneOf")); + } #[test] fn checked_in_repository_typescript_is_current() { diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 91e79e95a..510541fe8 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -14,7 +14,7 @@ use axum::http::header::{ use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri}; use axum::middleware::{self, Next}; use axum::response::{IntoResponse, Response}; -use axum::routing::{delete, get, post, put}; +use axum::routing::{get, post, put}; use axum::{Json, Router}; use chrono::{Duration, SecondsFormat, Utc}; use config_source::ConfigTreeSnapshot; @@ -48,16 +48,15 @@ use server_api::{ RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse, RepositorySshConnectionProbeRequest, RepositorySshConnectionProbeResponse, RepositorySshConnectionTrustState, RepositorySshCredential, RepositorySshHostKeyCandidate, - RepositorySshHostTrust, RepositorySshPublicKey, RequestActor, RevokeRuntimeTrustKeyRequest, + RepositorySshHostTrust, RepositorySshPublicKey, RequestActor, RotateRepositorySshCredentialRequest, RuntimeConnectionDisplayState, RuntimeConnectionTestFailureKind, RuntimeConnectionTestResponse, RuntimeConnectionTestStatus, RuntimeManagementSummary, RuntimeRemovalOperationResponse, RuntimeRemovalOperationState as ApiRuntimeRemovalOperationState, RuntimeTrustAuditAction, - RuntimeTrustAuditEntry, RuntimeTrustConflictKind, RuntimeTrustConflictResponse, - RuntimeTrustKeyRevealResponse, RuntimeTrustKeyState, RuntimeTrustKeyStatus, - UpdateRemoteRuntimeRequest, UpdateWorkspaceMetadataRequest, WhoamiResponse, - WorkerLaunchOptionsResponse, WorkerLaunchProfileCandidate, WorkerLaunchRuntimeOption, - WorkerLaunchWorkerSummary, + RuntimeTrustAuditEntry, RuntimeTrustKeyRevealResponse, RuntimeTrustKeyState, + RuntimeTrustKeyStatus, UpdateRemoteRuntimeRequest, UpdateWorkspaceMetadataRequest, + WhoamiResponse, WorkerLaunchOptionsResponse, WorkerLaunchProfileCandidate, + WorkerLaunchRuntimeOption, WorkerLaunchWorkerSummary, WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest, WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse, WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse, @@ -5049,6 +5048,791 @@ impl server_api::ServerApi for ServerApiContractService { }) } + async fn runtime_list_alias( + &self, + ) -> std::result::Result { + let Json(response) = list_runtimes(State(self.workspace_api()?.clone())) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_list( + &self, + workspace_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_list_runtimes( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_create( + &self, + actor: RequestActor, + workspace_id: String, + request: server_api::CreateRemoteRuntimeRequest, + ) -> std::result::Result + { + let (status, Json(mut response)) = scoped_create_remote_runtime( + State(self.workspace_api()?.clone()), + AxumPath(ScopedWorkspacePath { workspace_id }), + Extension(actor), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + response.http_status = status.as_u16(); + Ok(response) + } + + async fn runtime_detail( + &self, + workspace_id: String, + runtime_id: String, + ) -> std::result::Result + { + scoped_get_runtime_detail( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_update( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + request: server_api::UpdateRemoteRuntimeRequest, + ) -> std::result::Result + { + scoped_update_remote_runtime( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Extension(actor), + Json(request), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_remove( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + request: server_api::RemoveRuntimeRequest, + ) -> std::result::Result< + server_api::RuntimeRemovalOperationResponse, + server_api::RepositoryApiError, + > { + scoped_remove_remote_runtime( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Extension(actor), + Json(request), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_trust_key_reveal( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + ) -> std::result::Result< + server_api::RuntimeTrustKeyRevealResponse, + server_api::RepositoryApiError, + > { + scoped_reveal_runtime_trust_key( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_trust_key_revoke( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + request: server_api::RevokeRuntimeTrustKeyRequest, + ) -> std::result::Result< + server_api::WorkspaceRuntimeDetail, + server_api::RuntimeManagementApiError, + > { + let api = self + .workspace_api() + .map_err(server_api::RuntimeManagementApiError::from)? + .clone(); + let map_error = |error: ApiError| { + server_api::RuntimeManagementApiError::from(error.into_repository_api_error()) + }; + validate_workspace_scope(&api, &workspace_id).map_err(map_error)?; + require_workspace_owner(&api, &workspace_id, &actor, "Runtime trust changes") + .await + .map_err(map_error)?; + if runtime_id == EMBEDDED_WORKER_RUNTIME_ID { + return Err(map_error(settings_bad_request( + "embedded_runtime_trust_managed_internally", + "the embedded Runtime trust key is managed by the embedded Runtime authority", + ))); + } + if request.expected_revision == 0 { + return Err(map_error(settings_bad_request( + "invalid_runtime_binding_revision", + "expected_revision must be greater than zero", + ))); + } + let now = Utc::now().to_rfc3339(); + if let Err(error) = api + .store + .revoke_workspace_runtime_binding_key( + &workspace_id, + &runtime_id, + request.expected_revision, + &actor.account_id, + &now, + ) + .await + { + let kind = match error { + Error::RuntimeBindingRevisionConflict { .. } => { + Some(server_api::RuntimeTrustConflictKind::StaleRevision) + } + Error::RuntimeBindingFingerprintConflict { .. } => { + Some(server_api::RuntimeTrustConflictKind::FingerprintInUse) + } + _ => None, + }; + if let Some(kind) = kind { + let current = api + .store + .get_workspace_runtime_binding(&workspace_id, &runtime_id) + .await + .map_err(|error| map_error(ApiError::from(error)))?; + return Err(server_api::RuntimeManagementApiError::trust_conflict( + server_api::RuntimeTrustConflictResponse { + error: kind, + message: match kind { + server_api::RuntimeTrustConflictKind::StaleRevision => { + "the Runtime trust binding changed; reload before retrying".to_string() + } + server_api::RuntimeTrustConflictKind::FingerprintInUse => { + "the public key is already bound to another Runtime in this Workspace".to_string() + } + }, + current_revision: current.as_ref().map(|binding| binding.binding_revision), + current_fingerprint: current + .as_ref() + .map(|binding| binding.public_key_fingerprint.clone()), + }, + )); + } + return Err(map_error(ApiError::from(error))); + } + api.runtime_binding_expectations + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(&(workspace_id.clone(), runtime_id.clone())); + api.runtime_subscription_broker + .unregister_runtime(&runtime_id); + workspace_runtime_detail(&api, &workspace_id, &runtime_id) + .await + .map_err(map_error) + } + + async fn runtime_connection_test( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + ) -> std::result::Result< + server_api::RuntimeConnectionTestResponse, + server_api::RepositoryApiError, + > { + scoped_test_runtime_connection( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Extension(actor), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_worker_list_alias( + &self, + runtime_id: String, + query: server_api::RuntimeWorkersQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = list_runtime_workers( + State(self.workspace_api()?.clone()), + AxumPath(runtime_id), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_create_alias( + &self, + runtime_id: String, + request: server_api::RuntimeWorkerSpawnRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = create_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(runtime_id), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_list( + &self, + workspace_id: String, + runtime_id: String, + query: server_api::RuntimeWorkersQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_list_runtime_workers( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_create( + &self, + workspace_id: String, + runtime_id: String, + request: server_api::RuntimeWorkerSpawnRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_create_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_config_bundle_sync_alias( + &self, + runtime_id: String, + request: server_api::RuntimeConfigBundleSyncRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = sync_runtime_config_bundle( + State(self.workspace_api()?.clone()), + AxumPath(runtime_id), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_config_bundle_sync( + &self, + workspace_id: String, + runtime_id: String, + request: server_api::RuntimeConfigBundleSyncRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_sync_runtime_config_bundle( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_config_bundle_availability_alias( + &self, + runtime_id: String, + bundle_id: String, + query: server_api::RuntimeConfigBundleAvailabilityQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = check_runtime_config_bundle( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, bundle_id)), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_config_bundle_availability( + &self, + workspace_id: String, + runtime_id: String, + bundle_id: String, + query: server_api::RuntimeConfigBundleAvailabilityQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_check_runtime_config_bundle( + State(self.workspace_api()?.clone()), + AxumPath(ScopedConfigBundlePath { + workspace_id, + runtime_id, + bundle_id, + }), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_detail_alias( + &self, + runtime_id: String, + worker_id: String, + ) -> std::result::Result + { + let Json(response) = get_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_detail( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_get_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_restore_alias( + &self, + runtime_id: String, + worker_id: String, + ) -> std::result::Result + { + restore_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_worker_restore( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + query: server_api::RestoreTicketAssignmentQuery, + ) -> std::result::Result + { + let query = project_contract_dto(&query)?; + scoped_restore_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Query(query), + ) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + + async fn runtime_worker_pin( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_pin_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_unpin( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_unpin_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_cleanup_plan( + &self, + workspace_id: String, + runtime_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_runtime_cleanup_plan( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_cleanup_execute( + &self, + workspace_id: String, + runtime_id: String, + request: server_api::ExecuteRuntimeCleanupRequest, + ) -> std::result::Result< + server_api::RuntimeCleanupExecutionResponse, + server_api::RepositoryApiError, + > { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_execute_runtime_cleanup( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimePath { + workspace_id, + runtime_id, + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_input_alias( + &self, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerInputRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = send_runtime_worker_input( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_input( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerInputRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_send_runtime_worker_input( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_attachment_upload_grant( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + worker_id: String, + query: server_api::WorkerFileUploadQuery, + ) -> std::result::Result< + server_api::AttachmentUploadGrantResponse, + server_api::RepositoryApiError, + > { + let query = project_contract_dto(&query)?; + let Json(response) = scoped_create_attachment_upload_grant( + State(self.workspace_api()?.clone()), + Extension(actor), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Query(query), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_attachment_upload_cancel( + &self, + actor: RequestActor, + workspace_id: String, + runtime_id: String, + worker_id: String, + upload_id: String, + ) -> std::result::Result< + server_api::AttachmentUploadCancelResponse, + server_api::RepositoryApiError, + > { + let Json(response) = scoped_cancel_attachment_upload( + State(self.workspace_api()?.clone()), + Extension(actor), + AxumPath(ScopedAttachmentUploadPath { + workspace_id, + runtime_id, + worker_id, + upload_id, + }), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_attachment_delete( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + artifact_id: String, + ) -> std::result::Result + { + let Json(response) = scoped_delete_runtime_worker_uploaded_file( + State(self.workspace_api()?.clone()), + AxumPath(( + ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }, + artifact_id, + )), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_completions_alias( + &self, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerCompletionsRequest, + ) -> std::result::Result< + server_api::RuntimeWorkerCompletionsResult, + server_api::RepositoryApiError, + > { + let request = project_contract_dto(&request)?; + let Json(response) = runtime_worker_completions( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_completions( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerCompletionsRequest, + ) -> std::result::Result< + server_api::RuntimeWorkerCompletionsResult, + server_api::RepositoryApiError, + > { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_runtime_worker_completions( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_stop_alias( + &self, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerLifecycleRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = stop_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_stop( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerLifecycleRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_stop_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_cancel_alias( + &self, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerLifecycleRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = cancel_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath((runtime_id, worker_id)), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + + async fn runtime_worker_cancel( + &self, + workspace_id: String, + runtime_id: String, + worker_id: String, + request: server_api::RuntimeWorkerLifecycleRequest, + ) -> std::result::Result + { + let request = project_contract_dto(&request)?; + let Json(response) = scoped_cancel_runtime_worker( + State(self.workspace_api()?.clone()), + AxumPath(ScopedRuntimeWorkerPath { + workspace_id, + worker: RuntimeWorkerRef::new(runtime_id, worker_id), + }), + Json(request), + ) + .await + .map_err(ApiError::into_repository_api_error)?; + project_contract_dto(&response) + } + async fn repository_list( &self, workspace_id: String, @@ -6630,25 +7414,6 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/api/w/{workspace_id}/runtime-config", get(get_latest_workspace_runtime_config), ) - .route("/api/runtimes", get(list_runtimes)) - .route( - "/api/w/{workspace_id}/runtimes", - get(scoped_list_runtimes).post(scoped_create_remote_runtime), - ) - .route( - "/api/w/{workspace_id}/runtimes/{runtime_id}", - get(scoped_get_runtime_detail) - .post(scoped_update_remote_runtime) - .delete(scoped_remove_remote_runtime), - ) - .route( - "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key", - get(scoped_reveal_runtime_trust_key).delete(scoped_revoke_runtime_trust_key), - ) - .route( - "/api/w/{workspace_id}/runtimes/{runtime_id}/connection-tests", - post(scoped_test_runtime_connection), - ) .route( "/api/workers", get(list_workers).post(create_workspace_worker), @@ -6738,104 +7503,11 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/api/w/{workspace_id}/workers/remove", post(scoped_worker_remove_source_boundary), ) - .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/runtimes/{runtime_id}/workers/{worker_id}/restore", - post(restore_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}/restore", - post(scoped_restore_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/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-upload-grants", - post(scoped_create_attachment_upload_grant), - ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-uploads/{upload_id}", put(scoped_upload_runtime_worker_file) - .delete(scoped_cancel_attachment_upload) .layer(DefaultBodyLimit::max(MAX_WORKER_FILE_UPLOAD_BYTES)), ) - .route( - "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachments/{artifact_id}", - delete(scoped_delete_runtime_worker_uploaded_file), - ) - .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), @@ -16713,101 +17385,6 @@ async fn scoped_reveal_runtime_trust_key( })) } -async fn scoped_revoke_runtime_trust_key( - State(api): State, - AxumPath(path): AxumPath, - Extension(actor): Extension, - Json(request): Json, -) -> std::result::Result { - validate_workspace_scope(&api, &path.workspace_id)?; - require_workspace_owner(&api, &path.workspace_id, &actor, "Runtime trust changes").await?; - let actor_account_id = actor.account_id.clone(); - if path.runtime_id == EMBEDDED_WORKER_RUNTIME_ID { - return Err(settings_bad_request( - "embedded_runtime_trust_managed_internally", - "the embedded Runtime trust key is managed by the embedded Runtime authority", - )); - } - if request.expected_revision == 0 { - return Err(settings_bad_request( - "invalid_runtime_binding_revision", - "expected_revision must be greater than zero", - )); - } - let now = Utc::now().to_rfc3339(); - let mutation = api - .store - .revoke_workspace_runtime_binding_key( - &path.workspace_id, - &path.runtime_id, - request.expected_revision, - &actor_account_id, - &now, - ) - .await; - let _ = match mutation { - Ok(result) => result, - Err(error) => { - if let Some(response) = runtime_trust_conflict_response(&api, &path, &error).await { - return Ok(response); - } - return Err(error.into()); - } - }; - api.runtime_binding_expectations - .write() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .remove(&(path.workspace_id.clone(), path.runtime_id.clone())); - api.runtime_subscription_broker - .unregister_runtime(&path.runtime_id); - Ok( - Json(workspace_runtime_detail(&api, &path.workspace_id, &path.runtime_id).await?) - .into_response(), - ) -} - -async fn runtime_trust_conflict_response( - api: &WorkspaceApi, - path: &ScopedRuntimePath, - error: &Error, -) -> Option { - let kind = match error { - Error::RuntimeBindingRevisionConflict { .. } => RuntimeTrustConflictKind::StaleRevision, - Error::RuntimeBindingFingerprintConflict { .. } => { - RuntimeTrustConflictKind::FingerprintInUse - } - _ => return None, - }; - let current = api - .store - .get_workspace_runtime_binding(&path.workspace_id, &path.runtime_id) - .await - .ok() - .flatten(); - Some( - ( - StatusCode::CONFLICT, - Json(RuntimeTrustConflictResponse { - error: kind, - message: match kind { - RuntimeTrustConflictKind::StaleRevision => { - "the Runtime trust binding changed; reload before retrying".to_string() - } - RuntimeTrustConflictKind::FingerprintInUse => { - "the public key is already bound to another Runtime in this Workspace" - .to_string() - } - }, - current_revision: current.as_ref().map(|binding| binding.binding_revision), - current_fingerprint: current - .as_ref() - .map(|binding| binding.public_key_fingerprint.clone()), - }), - ) - .into_response(), - ) -} - async fn scoped_remove_remote_runtime( State(api): State, AxumPath(path): AxumPath, @@ -21059,6 +21636,7 @@ async fn workspace_runtime_resources_response( runtime_binding_summary(binding, verifications.get(&binding.runtime_id)) }), }, + http_status: StatusCode::OK.as_u16(), } }) .collect::>(); @@ -21108,6 +21686,7 @@ async fn workspace_runtime_resources_response( verifications.get(&binding.runtime_id), )), }, + http_status: StatusCode::OK.as_u16(), }); } @@ -21229,6 +21808,7 @@ async fn workspace_runtime_detail( token_ref_configured: false, binding: Some(runtime_binding_summary(&binding, None)), }, + http_status: StatusCode::OK.as_u16(), }); } let mut resource = resource.ok_or_else(|| Error::UnknownRuntime(runtime_id.to_string()))?; @@ -32802,34 +33382,30 @@ mod tests { .unwrap_err(); assert_eq!(denied.into_response().status(), StatusCode::FORBIDDEN); - let denied = scoped_revoke_runtime_trust_key( - State(api.clone()), - AxumPath(ScopedRuntimePath { - workspace_id: TEST_WORKSPACE_ID.to_owned(), - runtime_id: "runtime-a".to_owned(), - }), - Extension(non_owner), - Json(RevokeRuntimeTrustKeyRequest { + let contract_service = ServerApiContractService::Workspace(api.clone()); + let denied = ::runtime_trust_key_revoke( + &contract_service, + non_owner, + TEST_WORKSPACE_ID.to_owned(), + "runtime-a".to_owned(), + server_api::RevokeRuntimeTrustKeyRequest { expected_revision: 1, - }), + }, ) .await .unwrap_err(); - assert_eq!(denied.into_response().status(), StatusCode::FORBIDDEN); - let response = scoped_revoke_runtime_trust_key( - State(api.clone()), - AxumPath(ScopedRuntimePath { - workspace_id: TEST_WORKSPACE_ID.to_owned(), - runtime_id: "runtime-a".to_owned(), - }), - Extension(owner), - Json(RevokeRuntimeTrustKeyRequest { + assert_eq!(denied.error, "Forbidden"); + ::runtime_trust_key_revoke( + &contract_service, + owner, + TEST_WORKSPACE_ID.to_owned(), + "runtime-a".to_owned(), + server_api::RevokeRuntimeTrustKeyRequest { expected_revision: 1, - }), + }, ) .await .unwrap(); - assert_eq!(response.status(), StatusCode::OK); let binding = api .store .get_workspace_runtime_binding(TEST_WORKSPACE_ID, "runtime-a") diff --git a/openapi/server-api.json b/openapi/server-api.json index 19327a623..3a47d8674 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -81,6 +81,37 @@ }, "type": "array" }, + "AttachmentUploadCancelResponse": { + "additionalProperties": false, + "properties": { + "cancelled": { + "type": "boolean" + } + }, + "required": [ + "cancelled" + ], + "type": "object" + }, + "AttachmentUploadGrantResponse": { + "additionalProperties": false, + "properties": { + "expires_at_ms": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "upload_id": { + "type": "string" + } + }, + "required": [ + "upload_id", + "expires_at_ms" + ], + "type": "object" + }, "AuditEvent": { "oneOf": [ { @@ -455,6 +486,179 @@ ], "type": "string" }, + "CleanupTargetKind": { + "enum": [ + "worker_delete", + "workdir_clean_cleanup", + "workdir_dirty_discard", + "workdir_record_delete" + ], + "type": "string" + }, + "CleanupWorkdirCandidate": { + "additionalProperties": false, + "properties": { + "action": { + "$ref": "#/components/schemas/CleanupTargetKind" + }, + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "cleanliness": { + "$ref": "#/components/schemas/CleanupWorkdirCleanliness" + }, + "estimated_reclaim_bytes": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "file_status": { + "$ref": "#/components/schemas/CleanupWorkdirFileStatus" + }, + "linked_running_worker_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_worker_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pinned_linked": { + "type": "boolean" + }, + "reason": { + "type": "string" + }, + "repository_key": { + "type": "string" + }, + "running_linked": { + "type": "boolean" + }, + "runtime_id": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "workdir_id": { + "type": "string" + } + }, + "required": [ + "target_id", + "action", + "workdir_id", + "runtime_id", + "repository_key", + "reason", + "linked_worker_ids", + "linked_running_worker_ids", + "running_linked", + "pinned_linked", + "file_status", + "cleanliness" + ], + "type": "object" + }, + "CleanupWorkdirCleanliness": { + "enum": [ + "clean", + "dirty", + "unknown" + ], + "type": "string" + }, + "CleanupWorkdirFileStatus": { + "enum": [ + "pending", + "present", + "active", + "cleanup_pending", + "not_found", + "corrupted", + "failed", + "unknown" + ], + "type": "string" + }, + "CleanupWorkerCandidate": { + "additionalProperties": false, + "properties": { + "action": { + "$ref": "#/components/schemas/CleanupTargetKind" + }, + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "estimated_reclaim_bytes": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "linked_workdir_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pinned": { + "type": "boolean" + }, + "reason": { + "type": "string" + }, + "retention_state": { + "type": "string" + }, + "running_linked": { + "type": "boolean" + }, + "runtime_id": { + "type": "string" + }, + "runtime_worker_id": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "target_id", + "action", + "worker_id", + "runtime_worker_id", + "runtime_id", + "reason", + "pinned", + "retention_state", + "linked_workdir_ids", + "running_linked" + ], + "type": "object" + }, "ClearTicketRoleAssignmentQuery": { "additionalProperties": false, "properties": { @@ -1060,6 +1264,37 @@ }, "type": "object" }, + "CreateRemoteRuntimeRequest": { + "additionalProperties": false, + "properties": { + "display_name": { + "type": [ + "string", + "null" + ] + }, + "endpoint": { + "type": "string" + }, + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "public_bundle": { + "$ref": "#/components/schemas/RuntimePublicIdentityBundle" + } + }, + "required": [ + "public_bundle", + "endpoint" + ], + "type": "object" + }, "CreateRepositorySshCredentialRequest": { "additionalProperties": false, "properties": { @@ -1636,6 +1871,43 @@ ], "type": "string" }, + "ExecuteRuntimeCleanupRequest": { + "additionalProperties": false, + "properties": { + "confirm_dirty_discard_target_ids": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "expected_plan_digest": { + "type": "string" + }, + "expected_plan_revision": { + "type": "string" + }, + "workdir_target_ids": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "worker_target_ids": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "expected_plan_revision", + "expected_plan_digest" + ], + "type": "object" + }, "ExtensibleTicketStatus": { "oneOf": [ { @@ -5451,6 +5723,25 @@ ], "type": "object" }, + "RemoveRuntimeRequest": { + "additionalProperties": false, + "properties": { + "expected_binding_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "operation_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "expected_binding_revision" + ], + "type": "object" + }, "RepairMergeRequestSelectorRequest": { "additionalProperties": false, "properties": { @@ -6175,6 +6466,24 @@ ], "type": "object" }, + "RestoreTicketAssignmentQuery": { + "additionalProperties": false, + "properties": { + "assignment_operation_id": { + "type": [ + "string", + "null" + ] + }, + "ticket_id": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, "ReviewDecision": { "enum": [ "approve", @@ -6327,6 +6636,21 @@ ], "type": "object" }, + "RevokeRuntimeTrustKeyRequest": { + "additionalProperties": false, + "properties": { + "expected_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "expected_revision" + ], + "type": "object" + }, "RotateRepositorySshCredentialRequest": { "additionalProperties": false, "properties": { @@ -6357,6 +6681,1427 @@ ], "type": "object" }, + "RuntimeCleanupExecutionResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "executed_at": { + "type": "string" + }, + "plan_after": { + "$ref": "#/components/schemas/RuntimeCleanupPlanResponse" + }, + "results": { + "items": { + "$ref": "#/components/schemas/RuntimeCleanupExecutionResult" + }, + "type": "array" + }, + "runtime_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "executed_at", + "results", + "plan_after", + "diagnostics" + ], + "type": "object" + }, + "RuntimeCleanupExecutionResult": { + "additionalProperties": false, + "properties": { + "action": { + "$ref": "#/components/schemas/CleanupTargetKind" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + }, + "target_id": { + "type": "string" + } + }, + "required": [ + "target_id", + "action", + "status", + "message" + ], + "type": "object" + }, + "RuntimeCleanupPlanResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "digest": { + "type": "string" + }, + "generated_at": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "workdirs": { + "items": { + "$ref": "#/components/schemas/CleanupWorkdirCandidate" + }, + "type": "array" + }, + "workers": { + "items": { + "$ref": "#/components/schemas/CleanupWorkerCandidate" + }, + "type": "array" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "generated_at", + "revision", + "digest", + "workers", + "workdirs", + "diagnostics" + ], + "type": "object" + }, + "RuntimeConfigBundleAvailabilityQuery": { + "additionalProperties": false, + "properties": { + "digest": { + "type": "string" + } + }, + "required": [ + "digest" + ], + "type": "object" + }, + "RuntimeConfigBundleResult": { + "additionalProperties": false, + "properties": { + "availability": true, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "state": { + "$ref": "#/components/schemas/WorkerOperationState" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + "RuntimeConfigBundleSyncRequest": { + "additionalProperties": false, + "properties": { + "bundle": true + }, + "required": [ + "bundle" + ], + "type": "object" + }, + "RuntimeConnectionDisplayState": { + "enum": [ + "configured", + "verified", + "unavailable", + "revoked" + ], + "type": "string" + }, + "RuntimeConnectionTestFailureKind": { + "enum": [ + "authentication", + "authorization", + "network_unreachable", + "timeout", + "tls_or_transport", + "malformed_response", + "protocol_version_mismatch", + "runtime_identity_mismatch", + "configuration" + ], + "type": "string" + }, + "RuntimeConnectionTestResponse": { + "additionalProperties": false, + "properties": { + "actual_protocol_version": { + "format": "uint32", + "maximum": 4294967295, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "binding_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "checked_at": { + "type": "string" + }, + "connection_state": { + "$ref": "#/components/schemas/RuntimeConnectionDisplayState" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "expected_protocol_version": { + "format": "uint32", + "maximum": 4294967295, + "minimum": 0, + "type": "integer" + }, + "failure_kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeConnectionTestFailureKind" + }, + { + "type": "null" + } + ] + }, + "runtime_id": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/RuntimeConnectionTestStatus" + }, + "verification": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeVerificationEvidenceSummary" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "binding_revision", + "connection_state", + "checked_at", + "status", + "expected_protocol_version" + ], + "type": "object" + }, + "RuntimeConnectionTestStatus": { + "enum": [ + "compatible", + "failed" + ], + "type": "string" + }, + "RuntimeIdentityAuthority": { + "enum": [ + "runtime_registry_projection", + "server_runtime_configuration" + ], + "type": "string" + }, + "RuntimeListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/RuntimeSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "source" + ], + "type": "object" + }, + "RuntimeManagementApiError": { + "additionalProperties": false, + "properties": { + "current_fingerprint": { + "type": [ + "string", + "null" + ] + }, + "current_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ], + "type": "object" + }, + "RuntimeManagementSummary": { + "additionalProperties": false, + "properties": { + "binding": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkspaceRuntimeBindingSummary" + }, + { + "type": "null" + } + ] + }, + "built_in": { + "type": "boolean" + }, + "config_managed": { + "type": "boolean" + }, + "endpoint_configured": { + "type": "boolean" + }, + "removable": { + "type": "boolean" + }, + "token_ref_configured": { + "type": "boolean" + } + }, + "required": [ + "built_in", + "config_managed", + "removable", + "endpoint_configured", + "token_ref_configured" + ], + "type": "object" + }, + "RuntimeProfileSelector": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "builtin", + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "named", + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + } + ] + }, + "RuntimePublicIdentityBundle": { + "additionalProperties": false, + "properties": { + "identity_id": { + "type": "string" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "identity_id", + "public_key" + ], + "type": "object" + }, + "RuntimeRemovalOperationResponse": { + "additionalProperties": false, + "properties": { + "binding_removed": { + "type": "boolean" + }, + "completed_at": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "failure_category": { + "type": [ + "string", + "null" + ] + }, + "operation_id": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "runtime_registration_removed": { + "type": [ + "boolean", + "null" + ] + }, + "state": { + "$ref": "#/components/schemas/RuntimeRemovalOperationState" + }, + "updated_at": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "operation_id", + "workspace_id", + "runtime_id", + "state", + "binding_removed", + "created_at", + "updated_at" + ], + "type": "object" + }, + "RuntimeRemovalOperationState": { + "enum": [ + "pending", + "cleanup_pending", + "succeeded", + "failed" + ], + "type": "string" + }, + "RuntimeSourceKind": { + "enum": [ + "embedded_worker_runtime", + "remote_http" + ], + "type": "string" + }, + "RuntimeSourceStatus": { + "enum": [ + "active", + "reserved" + ], + "type": "string" + }, + "RuntimeSourceSummary": { + "additionalProperties": false, + "properties": { + "identity_authority": { + "$ref": "#/components/schemas/RuntimeIdentityAuthority" + }, + "kind": { + "$ref": "#/components/schemas/RuntimeSourceKind" + }, + "note": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/RuntimeSourceStatus" + } + }, + "required": [ + "kind", + "status", + "identity_authority", + "note" + ], + "type": "object" + }, + "RuntimeSummary": { + "properties": { + "arch": { + "type": "string" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "host_ids": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "kind": { + "type": "string" + }, + "label": { + "type": "string" + }, + "os": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/RuntimeSourceSummary" + }, + "status": { + "type": "string" + }, + "worker_creation_available": { + "type": "boolean" + } + }, + "required": [ + "runtime_id", + "label", + "kind", + "status", + "source", + "worker_creation_available", + "os", + "arch" + ], + "type": "object" + }, + "RuntimeTicketWorkerRole": { + "enum": [ + "intake", + "orchestrator", + "coder", + "reviewer" + ], + "type": "string" + }, + "RuntimeTrustAuditAction": { + "enum": [ + "created", + "replaced", + "reactivated", + "revoked" + ], + "type": "string" + }, + "RuntimeTrustAuditEntry": { + "additionalProperties": false, + "properties": { + "action": { + "$ref": "#/components/schemas/RuntimeTrustAuditAction" + }, + "actor_account_id": { + "type": "string" + }, + "at": { + "type": "string" + }, + "new_fingerprint": { + "type": [ + "string", + "null" + ] + }, + "old_fingerprint": { + "type": [ + "string", + "null" + ] + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "action", + "actor_account_id", + "revision", + "at" + ], + "type": "object" + }, + "RuntimeTrustKeyRevealResponse": { + "additionalProperties": false, + "properties": { + "public_key": { + "type": "string" + } + }, + "required": [ + "public_key" + ], + "type": "object" + }, + "RuntimeTrustKeyState": { + "additionalProperties": false, + "properties": { + "created_at": { + "type": [ + "string", + "null" + ] + }, + "fingerprint": { + "type": [ + "string", + "null" + ] + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "revoked_at": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/RuntimeTrustKeyStatus" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "RuntimeTrustKeyStatus": { + "enum": [ + "unconfigured", + "active", + "revoked" + ], + "type": "string" + }, + "RuntimeVerificationEvidenceSummary": { + "additionalProperties": false, + "properties": { + "binding_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "last_checked_at": { + "type": "string" + }, + "last_outcome": { + "$ref": "#/components/schemas/RuntimeVerificationOutcome" + }, + "runtime_identity_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "runtime_public_key_fingerprint": { + "type": "string" + }, + "verified_at": { + "type": [ + "string", + "null" + ] + }, + "workspace_identity_revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "workspace_key_id": { + "type": "string" + }, + "workspace_trust_generation": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "last_checked_at", + "last_outcome", + "binding_revision", + "workspace_key_id", + "workspace_identity_revision", + "workspace_trust_generation", + "runtime_public_key_fingerprint", + "runtime_identity_revision" + ], + "type": "object" + }, + "RuntimeVerificationOutcome": { + "enum": [ + "verified", + "challenge_issued", + "verification_failed", + "connectivity_failed" + ], + "type": "string" + }, + "RuntimeWorkerCompletionsRequest": { + "additionalProperties": false, + "properties": { + "kind": true, + "prefix": { + "default": "", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "RuntimeWorkerCompletionsResult": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "entries": { + "items": true, + "type": "array" + }, + "kind": true, + "prefix": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "runtime_id", + "worker_id", + "kind", + "prefix", + "entries" + ], + "type": "object" + }, + "RuntimeWorkerInputRequest": { + "additionalProperties": false, + "properties": { + "content": { + "type": "string" + }, + "kind": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "segments": { + "items": true, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "RuntimeWorkerInputResult": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "runtime_id": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/WorkerOperationState" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "state", + "runtime_id", + "worker_id" + ], + "type": "object" + }, + "RuntimeWorkerLifecycleRequest": { + "additionalProperties": false, + "properties": { + "reason": { + "type": [ + "string", + "null" + ] + }, + "ticket_assignment": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeWorkerTicketAssignmentRequest" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RuntimeWorkerLifecycleResult": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "runtime_id": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/WorkerOperationState" + }, + "worker_id": { + "type": "string" + } + }, + "required": [ + "state", + "runtime_id", + "worker_id" + ], + "type": "object" + }, + "RuntimeWorkerListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/WorkerSummary" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "source" + ], + "type": "object" + }, + "RuntimeWorkerShowResponse": { + "additionalProperties": false, + "description": "Workspace-authoritative Worker projection.\n\n`resource_key` is required here even though Runtime-internal Worker summaries\ndo not carry one. The Workspace Server must resolve it from Workspace\nauthority before constructing this response.", + "properties": { + "capabilities": { + "$ref": "#/components/schemas/WorkerCapabilitySummary" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "display_name": { + "default": "", + "type": "string" + }, + "host_id": { + "type": "string" + }, + "implementation": { + "$ref": "#/components/schemas/WorkerImplementationSummary" + }, + "label": { + "type": "string" + }, + "last_seen_at": { + "type": [ + "string", + "null" + ] + }, + "pinned": { + "default": false, + "type": "boolean" + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "resource_key": { + "type": "string" + }, + "retention_state": { + "default": "", + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "singleton_key": { + "type": [ + "string", + "null" + ] + }, + "state": { + "description": "Runtime catalog lifecycle compatibility state. Live foreground state, when\navailable, is carried separately in `worker_state`.", + "type": "string" + }, + "tags": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "type": "string" + }, + "workdir_attachments": { + "items": { + "$ref": "#/components/schemas/WorkerWorkdirAttachmentSummary" + }, + "type": "array" + }, + "worker_id": { + "type": "string" + }, + "worker_state": true, + "workspace": { + "$ref": "#/components/schemas/WorkerWorkspaceSummary" + } + }, + "required": [ + "runtime_id", + "worker_id", + "resource_key", + "host_id", + "label", + "workspace", + "state", + "implementation", + "capabilities", + "updated_at" + ], + "type": "object" + }, + "RuntimeWorkerSpawnAcceptanceEvidence": { + "additionalProperties": false, + "properties": { + "detail": { + "type": "string" + }, + "kind": { + "type": "string" + } + }, + "required": [ + "kind", + "detail" + ], + "type": "object" + }, + "RuntimeWorkerSpawnAcceptanceRequirement": { + "oneOf": [ + { + "enum": [ + "socket_ready" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "run_accepted": { + "properties": { + "expected_segments": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "expected_segments" + ], + "type": "object" + } + }, + "required": [ + "run_accepted" + ], + "type": "object" + } + ] + }, + "RuntimeWorkerSpawnIntent": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "workspace_companion", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "workspace_orchestrator", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "workspace_coding", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "ticket_role", + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/RuntimeTicketWorkerRole" + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "kind", + "ticket_id", + "role" + ], + "type": "object" + } + ] + }, + "RuntimeWorkerSpawnRequest": { + "additionalProperties": false, + "properties": { + "acceptance": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnAcceptanceRequirement" + }, + "initial_submit": { + "items": true, + "type": "array" + }, + "intent": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnIntent" + }, + "profile": { + "$ref": "#/components/schemas/RuntimeProfileSelector" + }, + "requested_worker_name": { + "type": [ + "string", + "null" + ] + }, + "ticket_assignment": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeWorkerTicketAssignmentRequest" + }, + { + "type": "null" + } + ] + }, + "workdir_attachment_requests": { + "items": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest" + }, + "type": "array" + } + }, + "required": [ + "intent", + "acceptance", + "profile" + ], + "type": "object" + }, + "RuntimeWorkerSpawnResponse": { + "additionalProperties": false, + "properties": { + "acceptance_evidence": { + "default": [], + "items": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnAcceptanceEvidence" + }, + "type": "array" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "state": { + "$ref": "#/components/schemas/WorkerOperationState" + }, + "worker": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeWorkerSummary" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "state" + ], + "type": "object" + }, + "RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest": { + "additionalProperties": false, + "properties": { + "alias": { + "type": "string" + }, + "working_directory": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnWorkingDirectoryRequest" + } + }, + "required": [ + "alias", + "working_directory" + ], + "type": "object" + }, + "RuntimeWorkerSpawnWorkingDirectoryRequest": { + "additionalProperties": false, + "properties": { + "repository_key": { + "type": "string" + }, + "selector": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "repository_key" + ], + "type": "object" + }, + "RuntimeWorkerSummary": { + "additionalProperties": false, + "properties": { + "capabilities": { + "$ref": "#/components/schemas/WorkerCapabilitySummary" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "display_name": { + "default": "", + "type": "string" + }, + "host_id": { + "type": "string" + }, + "implementation": { + "$ref": "#/components/schemas/WorkerImplementationSummary" + }, + "label": { + "type": "string" + }, + "last_seen_at": { + "type": [ + "string", + "null" + ] + }, + "pinned": { + "default": false, + "type": "boolean" + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "retention_state": { + "default": "", + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "singleton_key": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": "string" + }, + "tags": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "workdir_attachments": { + "items": true, + "type": "array" + }, + "worker_id": { + "type": "string" + }, + "worker_state": true, + "workspace": { + "$ref": "#/components/schemas/WorkerWorkspaceSummary" + } + }, + "required": [ + "runtime_id", + "worker_id", + "host_id", + "label", + "workspace", + "state", + "implementation", + "capabilities" + ], + "type": "object" + }, + "RuntimeWorkerTicketAssignmentRequest": { + "additionalProperties": false, + "properties": { + "operation_id": { + "type": "string" + }, + "ticket_id": { + "type": "string" + } + }, + "required": [ + "ticket_id", + "operation_id" + ], + "type": "object" + }, + "RuntimeWorkersQuery": { + "additionalProperties": false, + "properties": { + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeWorkersStatusFilter" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RuntimeWorkersStatusFilter": { + "enum": [ + "stopped" + ], + "type": "string" + }, "ServerJsonSafeU64": { "format": "uint64", "maximum": 9007199254740991, @@ -9222,6 +10967,24 @@ ], "type": "object" }, + "UpdateRemoteRuntimeRequest": { + "additionalProperties": false, + "properties": { + "display_name": { + "type": [ + "string", + "null" + ] + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "endpoint" + ], + "type": "object" + }, "UpdateWorkspaceMemorySettingsRequest": { "additionalProperties": false, "description": "Compare-and-swap update for Workspace-owned Memory settings.", @@ -9940,6 +11703,41 @@ ], "type": "object" }, + "WorkerFileDeleteResponse": { + "additionalProperties": false, + "properties": { + "deleted": { + "type": "boolean" + } + }, + "required": [ + "deleted" + ], + "type": "object" + }, + "WorkerFileUploadQuery": { + "additionalProperties": false, + "properties": { + "file_name": { + "type": "string" + }, + "media_type": { + "type": "string" + }, + "upload_id": { + "default": null, + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "file_name", + "media_type" + ], + "type": "object" + }, "WorkerImplementationSummary": { "properties": { "display_hint": { @@ -10042,6 +11840,105 @@ ], "type": "string" }, + "WorkerOperationState": { + "enum": [ + "accepted", + "unsupported", + "rejected" + ], + "type": "string" + }, + "WorkerRestoreResponse": { + "additionalProperties": false, + "properties": { + "result": { + "$ref": "#/components/schemas/WorkerRestoreResult" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "worker_id", + "result" + ], + "type": "object" + }, + "WorkerRestoreResult": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "state": { + "$ref": "#/components/schemas/WorkerRestoreState" + }, + "worker": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkerSummary" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "state" + ], + "type": "object" + }, + "WorkerRestoreState": { + "description": "Cross-layer outcome of an explicit Worker restore operation.\n\n`Rejected` is reserved for read-only preflight failures. Once live restore\nwork starts, failure is either a confirmed `RolledBack` operation or a\n`ReconciliationRequired` result whose commit state must be reread/retried.", + "enum": [ + "accepted", + "rejected", + "rolled_back", + "reconciliation_required" + ], + "type": "string" + }, + "WorkerRetentionResponse": { + "additionalProperties": false, + "properties": { + "pinned": { + "type": "boolean" + }, + "retention_state": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "worker_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime_id", + "worker_id", + "pinned", + "retention_state" + ], + "type": "object" + }, "WorkerSummary": { "description": "Workspace-authoritative Worker projection.\n\n`resource_key` is required here even though Runtime-internal Worker summaries\ndo not carry one. The Workspace Server must resolve it from Workspace\nauthority before constructing this response.", "properties": { @@ -11375,6 +13272,189 @@ ], "type": "object" }, + "WorkspaceRuntimeBindingState": { + "enum": [ + "configured", + "verified", + "revoked" + ], + "type": "string" + }, + "WorkspaceRuntimeBindingSummary": { + "additionalProperties": false, + "properties": { + "connection_state": { + "$ref": "#/components/schemas/RuntimeConnectionDisplayState" + }, + "revision": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "state": { + "$ref": "#/components/schemas/WorkspaceRuntimeBindingState" + }, + "verification": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeVerificationEvidenceSummary" + }, + { + "type": "null" + } + ] + }, + "workspace_key_generation": { + "format": "uint64", + "maximum": 9007199254740991, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "workspace_key_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "state", + "connection_state", + "revision" + ], + "type": "object" + }, + "WorkspaceRuntimeDetail": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": [ + "string", + "null" + ] + }, + "recent_audit": { + "default": [], + "items": { + "$ref": "#/components/schemas/RuntimeTrustAuditEntry" + }, + "type": "array" + }, + "runtime": { + "$ref": "#/components/schemas/WorkspaceRuntimeResource" + }, + "trust_key": { + "$ref": "#/components/schemas/RuntimeTrustKeyState" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "runtime", + "trust_key" + ], + "type": "object" + }, + "WorkspaceRuntimeListResponse": { + "additionalProperties": false, + "properties": { + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "items": { + "items": { + "$ref": "#/components/schemas/WorkspaceRuntimeResource" + }, + "type": "array" + }, + "limit": { + "format": "uint", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "source": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "limit", + "items", + "source" + ], + "type": "object" + }, + "WorkspaceRuntimeResource": { + "properties": { + "arch": { + "type": "string" + }, + "diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/Diagnostic" + }, + "type": "array" + }, + "host_ids": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "kind": { + "type": "string" + }, + "label": { + "type": "string" + }, + "management": { + "$ref": "#/components/schemas/RuntimeManagementSummary" + }, + "os": { + "type": "string" + }, + "runtime_id": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/RuntimeSourceSummary" + }, + "status": { + "type": "string" + }, + "worker_creation_available": { + "type": "boolean" + } + }, + "required": [ + "runtime_id", + "label", + "kind", + "status", + "source", + "worker_creation_available", + "os", + "arch", + "management" + ], + "type": "object" + }, "WorkspaceSigningIdentityPublic": { "additionalProperties": false, "description": "Public metadata for a Workspace signing identity. Private material and its\nstorage reference are deliberately not part of this wire authority.", @@ -11565,7 +13645,7 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59" + "x-yoi-source-digest": "sha256:67e56d1ffd3b27bc8653ef791bd68b5150ac7d507ccbe4e65b2e00320834d718" }, "openapi": "3.1.0", "paths": { @@ -12532,6 +14612,1399 @@ ] } }, + "/api/runtimes": { + "get": { + "operationId": "runtime_list_alias", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/config-bundles": { + "post": { + "operationId": "runtime_config_bundle_sync_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleSyncRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability": { + "get": { + "operationId": "runtime_config_bundle_availability_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "bundle_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleAvailabilityQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers": { + "get": { + "operationId": "runtime_worker_list_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RuntimeWorkersQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "runtime_worker_create_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}": { + "get": { + "operationId": "runtime_worker_detail_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerShowResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}/cancel": { + "post": { + "operationId": "runtime_worker_cancel_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}/completions": { + "post": { + "operationId": "runtime_worker_completions_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerCompletionsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerCompletionsResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}/input": { + "post": { + "operationId": "runtime_worker_input_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerInputRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerInputResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}/restore": { + "post": { + "operationId": "runtime_worker_restore_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkerRestoreResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/runtimes/{runtime_id}/workers/{worker_id}/stop": { + "post": { + "operationId": "runtime_worker_stop_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/tickets": { "get": { "operationId": "ticket_list_alias", @@ -16323,6 +19796,3339 @@ ] } }, + "/api/w/{workspace_id}/runtimes": { + "get": { + "operationId": "runtime_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "runtime_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRemoteRuntimeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeResource" + } + } + }, + "description": "Alternate successful response" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeResource" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}": { + "delete": { + "operationId": "runtime_remove", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoveRuntimeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeRemovalOperationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "runtime_detail", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "runtime_update", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateRemoteRuntimeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-executions": { + "post": { + "operationId": "runtime_cleanup_execute", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteRuntimeCleanupRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeCleanupExecutionResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan": { + "get": { + "operationId": "runtime_cleanup_plan", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeCleanupPlanResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles": { + "post": { + "operationId": "runtime_config_bundle_sync", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleSyncRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability": { + "get": { + "operationId": "runtime_config_bundle_availability", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "bundle_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleAvailabilityQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConfigBundleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/connection-tests": { + "post": { + "operationId": "runtime_connection_test", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeConnectionTestResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/trust-key": { + "delete": { + "operationId": "runtime_trust_key_revoke", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevokeRuntimeTrustKeyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceRuntimeDetail" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeManagementApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "get": { + "operationId": "runtime_trust_key_reveal", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeTrustKeyRevealResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers": { + "get": { + "operationId": "runtime_worker_list", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RuntimeWorkersQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "post": { + "operationId": "runtime_worker_create", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerSpawnResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}": { + "get": { + "operationId": "runtime_worker_detail", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerShowResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-upload-grants": { + "post": { + "operationId": "runtime_worker_attachment_upload_grant", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/WorkerFileUploadQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttachmentUploadGrantResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachment-uploads/{upload_id}": { + "delete": { + "operationId": "runtime_worker_attachment_upload_cancel", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "upload_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttachmentUploadCancelResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/attachments/{artifact_id}": { + "delete": { + "operationId": "runtime_worker_attachment_delete", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "artifact_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkerFileDeleteResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/cancel": { + "post": { + "operationId": "runtime_worker_cancel", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/completions": { + "post": { + "operationId": "runtime_worker_completions", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerCompletionsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerCompletionsResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/input": { + "post": { + "operationId": "runtime_worker_input", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerInputRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerInputResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin": { + "delete": { + "operationId": "runtime_worker_unpin", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkerRetentionResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + }, + "put": { + "operationId": "runtime_worker_pin", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkerRetentionResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/restore": { + "post": { + "operationId": "runtime_worker_restore", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": true, + "in": "query", + "name": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/RestoreTicketAssignmentQuery" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkerRestoreResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, + "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/stop": { + "post": { + "operationId": "runtime_worker_stop", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "workspace_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "runtime_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + }, + { + "explode": false, + "in": "path", + "name": "worker_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeWorkerLifecycleResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + }, + "security": [ + { + "bearerAuth": [] + }, + { + "browserSession": [] + } + ] + } + }, "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories": { "get": { "operationId": "runtime_workdir_list", diff --git a/web/workspace/src/lib/generated/legacy-server-api.ts b/web/workspace/src/lib/generated/legacy-server-api.ts index dd34705e3..2dd94fea6 100644 --- a/web/workspace/src/lib/generated/legacy-server-api.ts +++ b/web/workspace/src/lib/generated/legacy-server-api.ts @@ -359,207 +359,3 @@ export type RepositoryLogResponse = { items: Array; diagnostics: Array; }; - -export type RuntimeSourceKind = "embedded_worker_runtime" | "remote_http"; - -export type RuntimeSourceStatus = "active" | "reserved"; - -export type RuntimeIdentityAuthority = - | "runtime_registry_projection" - | "server_runtime_configuration"; - -export type RuntimeSourceSummary = { - kind: RuntimeSourceKind; - status: RuntimeSourceStatus; - identity_authority: RuntimeIdentityAuthority; - note: string; -}; - -export type RuntimeSummary = { - runtime_id: string; - label: string; - kind: string; - status: string; - source: RuntimeSourceSummary; - host_ids: Array; - worker_creation_available: boolean; - os: string; - arch: string; - diagnostics: Array; -}; - -export type WorkspaceRuntimeBindingState = - | "configured" - | "verified" - | "revoked"; - -export type RuntimeConnectionDisplayState = - | "configured" - | "verified" - | "unavailable" - | "revoked"; - -export type RuntimeVerificationOutcome = - | "verified" - | "challenge_issued" - | "verification_failed" - | "connectivity_failed"; - -export type RuntimeVerificationEvidenceSummary = { - verified_at: string | null; - last_checked_at: string; - last_outcome: RuntimeVerificationOutcome; - binding_revision: number; - workspace_key_id: string; - workspace_identity_revision: number; - workspace_trust_generation: number; - runtime_public_key_fingerprint: string; - runtime_identity_revision: number; -}; - -export type WorkspaceRuntimeBindingSummary = { - state: WorkspaceRuntimeBindingState; - connection_state: RuntimeConnectionDisplayState; - revision: number; - workspace_key_id?: string | null; - workspace_key_generation?: number | null; - verification?: RuntimeVerificationEvidenceSummary | null; -}; - -export type RuntimeManagementSummary = { - built_in: boolean; - config_managed: boolean; - removable: boolean; - endpoint_configured: boolean; - token_ref_configured: boolean; - binding?: WorkspaceRuntimeBindingSummary | null; -}; - -export type WorkspaceRuntimeResource = { - management: RuntimeManagementSummary; - runtime_id: string; - label: string; - kind: string; - status: string; - source: RuntimeSourceSummary; - host_ids: Array; - worker_creation_available: boolean; - os: string; - arch: string; - diagnostics: Array; -}; - -export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked"; - -export type RuntimeTrustKeyState = { - status: RuntimeTrustKeyStatus; - fingerprint?: string | null; - revision?: number | null; - created_at?: string | null; - updated_at?: string | null; - revoked_at?: string | null; -}; - -export type RuntimeTrustAuditAction = - | "created" - | "replaced" - | "reactivated" - | "revoked"; - -export type RuntimeTrustAuditEntry = { - action: RuntimeTrustAuditAction; - actor_account_id: string; - old_fingerprint?: string | null; - new_fingerprint?: string | null; - revision: number; - at: string; -}; - -export type WorkspaceRuntimeDetail = { - workspace_id: string; - runtime: WorkspaceRuntimeResource; - endpoint?: string | null; - trust_key: RuntimeTrustKeyState; - recent_audit: Array; -}; - -export type RuntimeTrustKeyRevealResponse = { public_key: string }; - -export type RevokeRuntimeTrustKeyRequest = { expected_revision: number }; - -export type RemoveRuntimeRequest = { - operation_id: string; - expected_binding_revision: number; -}; - -export type RuntimeRemovalOperationState = - | "pending" - | "cleanup_pending" - | "succeeded" - | "failed"; - -export type RuntimeRemovalOperationResponse = { - operation_id: string; - workspace_id: string; - runtime_id: string; - state: RuntimeRemovalOperationState; - binding_removed: boolean; - runtime_registration_removed: boolean | null; - failure_category?: string | null; - created_at: string; - updated_at: string; - completed_at?: string | null; -}; - -export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use"; - -export type RuntimeTrustConflictResponse = { - error: RuntimeTrustConflictKind; - message: string; - current_revision?: number; - current_fingerprint?: string | null; -}; - -export type RuntimePublicIdentityBundle = { - identity_id: string; - public_key: string; -}; - -export type CreateRemoteRuntimeRequest = { - public_bundle: RuntimePublicIdentityBundle; - display_name?: string | null; - endpoint: string; - expected_revision?: number | null; -}; - -export type UpdateRemoteRuntimeRequest = { - display_name?: string | null; - endpoint: string; -}; - -export type RuntimeConnectionTestStatus = "compatible" | "failed"; - -export type RuntimeConnectionTestFailureKind = - | "authentication" - | "authorization" - | "network_unreachable" - | "timeout" - | "tls_or_transport" - | "malformed_response" - | "protocol_version_mismatch" - | "runtime_identity_mismatch" - | "configuration"; - -export type RuntimeConnectionTestResponse = { - workspace_id: string; - runtime_id: string; - binding_revision: number; - connection_state: RuntimeConnectionDisplayState; - verification: RuntimeVerificationEvidenceSummary | null; - checked_at: string; - status: RuntimeConnectionTestStatus; - failure_kind: RuntimeConnectionTestFailureKind | null; - expected_protocol_version: number; - actual_protocol_version: number | null; - diagnostics: Array; -}; diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index 2da845e3a..b9d58d0e4 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59) +// Input contract: 0.1.0 (sha256:67e56d1ffd3b27bc8653ef791bd68b5150ac7d507ccbe4e65b2e00320834d718) // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Output: web/workspace/src/lib/generated/repository-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check diff --git a/web/workspace/src/lib/generated/runtime-api.ts b/web/workspace/src/lib/generated/runtime-api.ts new file mode 100644 index 000000000..bdc01ad91 --- /dev/null +++ b/web/workspace/src/lib/generated/runtime-api.ts @@ -0,0 +1,172 @@ +// @generated by yoi-runtime-openapi-typescript@1. Do not edit. +// Input: openapi/server-api.json +// Input contract: 0.1.0 (sha256:67e56d1ffd3b27bc8653ef791bd68b5150ac7d507ccbe4e65b2e00320834d718) +// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output +// Output: web/workspace/src/lib/generated/runtime-api.ts +// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check + +export type AttachmentUploadCancelResponse = { cancelled: boolean; }; + +export type AttachmentUploadGrantResponse = { expires_at_ms: number; upload_id: string; }; + +export type CleanupTargetKind = "worker_delete" | "workdir_clean_cleanup" | "workdir_dirty_discard" | "workdir_record_delete"; + +export type CleanupWorkdirCandidate = { action: CleanupTargetKind; blocking_reason?: string | null; cleanliness: CleanupWorkdirCleanliness; estimated_reclaim_bytes?: number | null; file_status: CleanupWorkdirFileStatus; linked_running_worker_ids: Array; linked_worker_ids: Array; pinned_linked: boolean; reason: string; repository_key: string; running_linked: boolean; runtime_id: string; target_id: string; workdir_id: string; }; + +export type CleanupWorkdirCleanliness = "clean" | "dirty" | "unknown"; + +export type CleanupWorkdirFileStatus = "pending" | "present" | "active" | "cleanup_pending" | "not_found" | "corrupted" | "failed" | "unknown"; + +export type CleanupWorkerCandidate = { action: CleanupTargetKind; blocking_reason?: string | null; estimated_reclaim_bytes?: number | null; linked_workdir_ids: Array; pinned: boolean; reason: string; retention_state: string; running_linked: boolean; runtime_id: string; runtime_worker_id: string; target_id: string; worker_id: string; }; + +export type CreateRemoteRuntimeRequest = { display_name?: string | null; endpoint: string; expected_revision?: number | null; public_bundle: RuntimePublicIdentityBundle; }; + +export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; }; + +export type DiagnosticSeverity = "info" | "warning" | "error"; + +export type ExecuteRuntimeCleanupRequest = { confirm_dirty_discard_target_ids?: Array; expected_plan_digest: string; expected_plan_revision: string; workdir_target_ids?: Array; worker_target_ids?: Array; }; + +export type RemoveRuntimeRequest = { expected_binding_revision: number; operation_id: string; }; + +export type RepositoryApiError = { diagnostics?: Array; error: string; message: string; }; + +export type RevokeRuntimeTrustKeyRequest = { expected_revision: number; }; + +export type RuntimeCleanupExecutionResponse = { diagnostics: Array; executed_at: string; plan_after: RuntimeCleanupPlanResponse; results: Array; runtime_id: string; workspace_id: string; }; + +export type RuntimeCleanupExecutionResult = { action: CleanupTargetKind; message: string; status: string; target_id: string; }; + +export type RuntimeCleanupPlanResponse = { diagnostics: Array; digest: string; generated_at: string; revision: string; runtime_id: string; workdirs: Array; workers: Array; workspace_id: string; }; + +export type RuntimeConfigBundleResult = { availability?: unknown; diagnostics?: Array; state: WorkerOperationState; }; + +export type RuntimeConfigBundleSyncRequest = { bundle: unknown; }; + +export type RuntimeConnectionDisplayState = "configured" | "verified" | "unavailable" | "revoked"; + +export type RuntimeConnectionTestFailureKind = "authentication" | "authorization" | "network_unreachable" | "timeout" | "tls_or_transport" | "malformed_response" | "protocol_version_mismatch" | "runtime_identity_mismatch" | "configuration"; + +export type RuntimeConnectionTestResponse = { actual_protocol_version?: number | null; binding_revision: number; checked_at: string; connection_state: RuntimeConnectionDisplayState; diagnostics?: Array; expected_protocol_version: number; failure_kind?: RuntimeConnectionTestFailureKind | null; runtime_id: string; status: RuntimeConnectionTestStatus; verification?: RuntimeVerificationEvidenceSummary | null; workspace_id: string; }; + +export type RuntimeConnectionTestStatus = "compatible" | "failed"; + +export type RuntimeIdentityAuthority = "runtime_registry_projection" | "server_runtime_configuration"; + +export type RuntimeListResponse = { diagnostics?: Array; items: Array; limit: number; source: string; workspace_id: string; }; + +export type RuntimeManagementApiError = { current_fingerprint?: string | null; current_revision?: number | null; diagnostics?: Array; error: string; message: string; }; + +export type RuntimeManagementSummary = { binding?: WorkspaceRuntimeBindingSummary | null; built_in: boolean; config_managed: boolean; endpoint_configured: boolean; removable: boolean; token_ref_configured: boolean; }; + +export type RuntimeProfileSelector = { kind: "builtin"; value: string; } | { kind: "named"; value: string; }; + +export type RuntimePublicIdentityBundle = { identity_id: string; public_key: string; }; + +export type RuntimeRemovalOperationResponse = { binding_removed: boolean; completed_at?: string | null; created_at: string; failure_category?: string | null; operation_id: string; runtime_id: string; runtime_registration_removed?: boolean | null; state: RuntimeRemovalOperationState; updated_at: string; workspace_id: string; }; + +export type RuntimeRemovalOperationState = "pending" | "cleanup_pending" | "succeeded" | "failed"; + +export type RuntimeSourceKind = "embedded_worker_runtime" | "remote_http"; + +export type RuntimeSourceStatus = "active" | "reserved"; + +export type RuntimeSourceSummary = { identity_authority: RuntimeIdentityAuthority; kind: RuntimeSourceKind; note: string; status: RuntimeSourceStatus; }; + +export type RuntimeSummary = { arch: string; diagnostics?: Array; host_ids?: Array; kind: string; label: string; os: string; runtime_id: string; source: RuntimeSourceSummary; status: string; worker_creation_available: boolean; }; + +export type RuntimeTicketWorkerRole = "intake" | "orchestrator" | "coder" | "reviewer"; + +export type RuntimeTrustAuditAction = "created" | "replaced" | "reactivated" | "revoked"; + +export type RuntimeTrustAuditEntry = { action: RuntimeTrustAuditAction; actor_account_id: string; at: string; new_fingerprint?: string | null; old_fingerprint?: string | null; revision: number; }; + +export type RuntimeTrustKeyRevealResponse = { public_key: string; }; + +export type RuntimeTrustKeyState = { created_at?: string | null; fingerprint?: string | null; revision?: number | null; revoked_at?: string | null; status: RuntimeTrustKeyStatus; updated_at?: string | null; }; + +export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked"; + +export type RuntimeVerificationEvidenceSummary = { binding_revision: number; last_checked_at: string; last_outcome: RuntimeVerificationOutcome; runtime_identity_revision: number; runtime_public_key_fingerprint: string; verified_at?: string | null; workspace_identity_revision: number; workspace_key_id: string; workspace_trust_generation: number; }; + +export type RuntimeVerificationOutcome = "verified" | "challenge_issued" | "verification_failed" | "connectivity_failed"; + +export type RuntimeWorkerCompletionsRequest = { kind: unknown; prefix?: string; }; + +export type RuntimeWorkerCompletionsResult = { diagnostics?: Array; entries: Array; kind: unknown; prefix: string; runtime_id: string; worker_id: string; }; + +export type RuntimeWorkerInputRequest = { content: string; kind?: string | null; segments?: Array | null; }; + +export type RuntimeWorkerInputResult = { diagnostics?: Array; runtime_id: string; state: WorkerOperationState; worker_id: string; }; + +export type RuntimeWorkerLifecycleRequest = { reason?: string | null; ticket_assignment?: RuntimeWorkerTicketAssignmentRequest | null; }; + +export type RuntimeWorkerLifecycleResult = { diagnostics?: Array; runtime_id: string; state: WorkerOperationState; worker_id: string; }; + +export type RuntimeWorkerListResponse = { diagnostics?: Array; items: Array; limit: number; source: string; workspace_id: string; }; + +export type RuntimeWorkerShowResponse = { capabilities: WorkerCapabilitySummary; diagnostics?: Array; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array; updated_at: string; workdir_attachments?: Array; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; }; + +export type RuntimeWorkerSpawnAcceptanceEvidence = { detail: string; kind: string; }; + +export type RuntimeWorkerSpawnAcceptanceRequirement = "socket_ready" | { run_accepted: { expected_segments: number; }; }; + +export type RuntimeWorkerSpawnIntent = { kind: "workspace_companion"; } | { kind: "workspace_orchestrator"; } | { kind: "workspace_coding"; } | { kind: "ticket_role"; role: RuntimeTicketWorkerRole; ticket_id: string; }; + +export type RuntimeWorkerSpawnRequest = { acceptance: RuntimeWorkerSpawnAcceptanceRequirement; initial_submit?: Array; intent: RuntimeWorkerSpawnIntent; profile: RuntimeProfileSelector; requested_worker_name?: string | null; ticket_assignment?: RuntimeWorkerTicketAssignmentRequest | null; workdir_attachment_requests?: Array; }; + +export type RuntimeWorkerSpawnResponse = { acceptance_evidence?: Array; diagnostics?: Array; state: WorkerOperationState; worker?: RuntimeWorkerSummary | null; }; + +export type RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest = { alias: string; working_directory: RuntimeWorkerSpawnWorkingDirectoryRequest; }; + +export type RuntimeWorkerSpawnWorkingDirectoryRequest = { repository_key: string; selector?: string | null; }; + +export type RuntimeWorkerSummary = { capabilities: WorkerCapabilitySummary; diagnostics?: Array; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array; workdir_attachments?: Array; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; }; + +export type RuntimeWorkerTicketAssignmentRequest = { operation_id: string; ticket_id: string; }; + +export type UpdateRemoteRuntimeRequest = { display_name?: string | null; endpoint: string; }; + +export type WorkerCapabilitySummary = { can_spawn_followup: boolean; can_stop: boolean; }; + +export type WorkerFileDeleteResponse = { deleted: boolean; }; + +export type WorkerImplementationSummary = { display_hint: string; kind: string; }; + +export type WorkerOperationState = "accepted" | "unsupported" | "rejected"; + +export type WorkerRestoreResponse = { result: WorkerRestoreResult; runtime_id: string; worker_id: string; workspace_id: string; }; + +export type WorkerRestoreResult = { diagnostics?: Array; state: WorkerRestoreState; worker?: WorkerSummary | null; }; + +export type WorkerRestoreState = "accepted" | "rejected" | "rolled_back" | "reconciliation_required"; + +export type WorkerRetentionResponse = { pinned: boolean; retention_state: string; runtime_id: string; worker_id: string; workspace_id: string; }; + +export type WorkerSummary = { capabilities: WorkerCapabilitySummary; diagnostics?: Array; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array; workdir_attachments?: Array; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; }; + +export type WorkerWorkdirAttachmentSummary = { alias: string; working_directory: WorkingDirectorySummary; }; + +export type WorkerWorkspaceSummary = { identity: string; visibility: string; workspace_id?: string | null; }; + +export type WorkingDirectoryCleanupTarget = { kind: string; repository_key: string; working_directory_id: string; }; + +export type WorkingDirectoryMaterializerKind = "runtime_git_clone" | "client_hosted_external"; + +export type WorkingDirectoryOccupancy = { display_name: string; linked_at: string; runtime_id: string; worker_id: string; }; + +export type WorkingDirectorySource = { kind: "repository"; repository_key: string; } | { grant_id: string; kind: "external_grant"; }; + +export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown"; + +export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_target?: WorkingDirectoryCleanupTarget | null; creation_ref?: string | null; creation_selector?: string | null; creation_tree?: string | null; current_ref?: string | null; current_selector?: string | null; current_tree?: string | null; display_name?: string | null; materializer_kind: WorkingDirectoryMaterializerKind; observed_at_epoch_seconds?: number | null; occupied_by?: WorkingDirectoryOccupancy | null; source: WorkingDirectorySource; status: WorkingDirectoryStatusKind; working_directory_id: string; }; + +export type WorkspaceRuntimeBindingState = "configured" | "verified" | "revoked"; + +export type WorkspaceRuntimeBindingSummary = { connection_state: RuntimeConnectionDisplayState; revision: number; state: WorkspaceRuntimeBindingState; verification?: RuntimeVerificationEvidenceSummary | null; workspace_key_generation?: number | null; workspace_key_id?: string | null; }; + +export type WorkspaceRuntimeDetail = { endpoint?: string | null; recent_audit?: Array; runtime: WorkspaceRuntimeResource; trust_key: RuntimeTrustKeyState; workspace_id: string; }; + +export type WorkspaceRuntimeListResponse = { diagnostics?: Array; items: Array; limit: number; source: string; workspace_id: string; }; + +export type WorkspaceRuntimeResource = { arch: string; diagnostics?: Array; host_ids?: Array; kind: string; label: string; management: RuntimeManagementSummary; os: string; runtime_id: string; source: RuntimeSourceSummary; status: string; worker_creation_available: boolean; }; diff --git a/web/workspace/src/lib/generated/workdir-api.ts b/web/workspace/src/lib/generated/workdir-api.ts index c9b61fc1c..135e935f6 100644 --- a/web/workspace/src/lib/generated/workdir-api.ts +++ b/web/workspace/src/lib/generated/workdir-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-workdir-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:e3f776a17079ce202725bb97de607426f4bfad9afe390ba7bbf76186e871be59) +// Input contract: 0.1.0 (sha256:67e56d1ffd3b27bc8653ef791bd68b5150ac7d507ccbe4e65b2e00320834d718) // Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Output: web/workspace/src/lib/generated/workdir-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check diff --git a/web/workspace/src/lib/workspace/api/runtime-connection.ts b/web/workspace/src/lib/workspace/api/runtime-connection.ts index ada1ff6aa..7f308e685 100644 --- a/web/workspace/src/lib/workspace/api/runtime-connection.ts +++ b/web/workspace/src/lib/workspace/api/runtime-connection.ts @@ -3,7 +3,7 @@ import type { RuntimeConnectionTestFailureKind, RuntimeConnectionTestResponse, RuntimeVerificationEvidenceSummary, -} from "$lib/generated/legacy-server-api"; +} from "$lib/generated/runtime-api"; const RESPONSE_KEYS = [ "workspace_id", diff --git a/web/workspace/src/lib/workspace/api/runtime-management.ts b/web/workspace/src/lib/workspace/api/runtime-management.ts index 92cd1ba0a..b16355ed0 100644 --- a/web/workspace/src/lib/workspace/api/runtime-management.ts +++ b/web/workspace/src/lib/workspace/api/runtime-management.ts @@ -5,6 +5,7 @@ import type { RevokeRuntimeTrustKeyRequest, RuntimeConnectionDisplayState, RuntimeIdentityAuthority, + RuntimeManagementApiError, RuntimeManagementSummary, RuntimeRemovalOperationResponse, RuntimeRemovalOperationState, @@ -13,8 +14,6 @@ import type { RuntimeSourceSummary, RuntimeTrustAuditAction, RuntimeTrustAuditEntry, - RuntimeTrustConflictKind, - RuntimeTrustConflictResponse, RuntimeTrustKeyRevealResponse, RuntimeTrustKeyState, RuntimeTrustKeyStatus, @@ -23,12 +22,24 @@ import type { WorkspaceRuntimeBindingState, WorkspaceRuntimeBindingSummary, WorkspaceRuntimeDetail, + WorkspaceRuntimeListResponse, WorkspaceRuntimeResource, -} from "$lib/generated/legacy-server-api.ts"; -import type { ListResponse } from "$lib/workspace/sidebar/types"; +} from "$lib/generated/runtime-api.ts"; import { workspaceApiPath } from "./http.ts"; -export type WorkspaceRuntimeList = ListResponse; +export type WorkspaceRuntimeList = Omit & { + diagnostics: Diagnostic[]; +}; +export type ValidatedWorkspaceRuntimeDetail = Omit< + WorkspaceRuntimeDetail, + "recent_audit" +> & { + recent_audit: RuntimeTrustAuditEntry[]; +}; +export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use"; +export type RuntimeTrustConflictResponse = RuntimeManagementApiError & { + error: RuntimeTrustConflictKind; +}; const LIMITS = { runtimeItems: 200, @@ -690,7 +701,7 @@ export function parseWorkspaceRuntimeList( export function parseWorkspaceRuntimeDetail( value: unknown, -): WorkspaceRuntimeDetail { +): ValidatedWorkspaceRuntimeDetail { const response = object(value, "Runtime detail response"); exactKeys( response, diff --git a/web/workspace/src/lib/workspace/api/runtime-workers.ts b/web/workspace/src/lib/workspace/api/runtime-workers.ts new file mode 100644 index 000000000..18878ce66 --- /dev/null +++ b/web/workspace/src/lib/workspace/api/runtime-workers.ts @@ -0,0 +1,356 @@ +import type { + CleanupTargetKind, + CleanupWorkdirCandidate, + CleanupWorkdirCleanliness, + CleanupWorkdirFileStatus, + CleanupWorkerCandidate, + Diagnostic, + RuntimeCleanupExecutionResponse, + RuntimeCleanupExecutionResult, + RuntimeCleanupPlanResponse, + RuntimeWorkerLifecycleResult, + WorkerOperationState, + WorkerRetentionResponse, +} from "$lib/generated/runtime-api"; + +const MAX_STRING_BYTES = 4_096; +const MAX_CANDIDATES = 1_000; +const MAX_RESULTS = 2_000; +const MAX_DIAGNOSTICS = 64; +const encoder = new TextEncoder(); +type JsonObject = Record; + +const TARGET_KINDS = new Set([ + "worker_delete", + "workdir_clean_cleanup", + "workdir_dirty_discard", + "workdir_record_delete", +]); +const FILE_STATUSES = new Set([ + "pending", + "present", + "active", + "cleanup_pending", + "not_found", + "corrupted", + "failed", + "unknown", +]); +const CLEANLINESS = new Set([ + "clean", + "dirty", + "unknown", +]); + +export class RuntimeWorkerValidationError extends Error { + constructor(message: string) { + super(message.slice(0, 256)); + this.name = "RuntimeWorkerValidationError"; + } +} + +function fail(path: string, message: string): never { + throw new RuntimeWorkerValidationError(`${path} ${message}`); +} + +function object(value: unknown, path: string): JsonObject { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + return fail(path, "must be an object"); + } + return value as JsonObject; +} + +function exactKeys( + value: JsonObject, + required: readonly string[], + optional: readonly string[], + path: string, +): void { + const allowed = new Set([...required, ...optional]); + for (const key of Object.keys(value)) { + if (!allowed.has(key)) fail(`${path}.${key}`, "is not part of the wire contract"); + } + for (const key of required) { + if (!Object.hasOwn(value, key)) fail(`${path}.${key}`, "is required"); + } +} + +function boundedString(value: unknown, path: string, allowEmpty = false): string { + if (typeof value !== "string") return fail(path, "must be a string"); + if (!allowEmpty && value.length === 0) return fail(path, "must not be empty"); + if (encoder.encode(value).byteLength > MAX_STRING_BYTES) { + return fail(path, `must be at most ${MAX_STRING_BYTES} UTF-8 bytes`); + } + return value; +} + +function boolean(value: unknown, path: string): boolean { + if (typeof value !== "boolean") return fail(path, "must be a boolean"); + return value; +} + +function array(value: unknown, path: string, max: number): unknown[] { + if (!Array.isArray(value)) return fail(path, "must be an array"); + if (value.length > max) return fail(path, `must contain at most ${max} items`); + return value; +} + +function strings(value: unknown, path: string): string[] { + return array(value, path, MAX_CANDIDATES).map((entry, index) => + boundedString(entry, `${path}[${index}]`) + ); +} + +function optionalNullableString(value: unknown, path: string): string | null | undefined { + if (value === undefined || value === null) return value; + return boundedString(value, path); +} + +function optionalNullableSafeInteger(value: unknown, path: string): number | null | undefined { + if (value === undefined || value === null) return value; + if (!Number.isSafeInteger(value) || (value as number) < 0) { + return fail(path, "must be a non-negative safe integer"); + } + return value as number; +} + +function targetKind(value: unknown, path: string): CleanupTargetKind { + if (typeof value !== "string" || !TARGET_KINDS.has(value as CleanupTargetKind)) { + return fail(path, "has an unknown cleanup action"); + } + return value as CleanupTargetKind; +} + +function diagnostic(value: unknown, path: string): Diagnostic { + const item = object(value, path); + exactKeys(item, ["code", "severity", "message"], [], path); + const severity = boundedString(item.severity, `${path}.severity`); + if (severity !== "info" && severity !== "warning" && severity !== "error") { + fail(`${path}.severity`, "has an unknown value"); + } + return { + code: boundedString(item.code, `${path}.code`), + severity, + message: boundedString(item.message, `${path}.message`, true), + }; +} + +function diagnostics(value: unknown, path: string): Diagnostic[] { + return array(value, path, MAX_DIAGNOSTICS).map((entry, index) => + diagnostic(entry, `${path}[${index}]`) + ); +} + +function workerCandidate(value: unknown, path: string): CleanupWorkerCandidate { + const item = object(value, path); + exactKeys( + item, + [ + "target_id", + "action", + "worker_id", + "runtime_worker_id", + "runtime_id", + "reason", + "pinned", + "retention_state", + "linked_workdir_ids", + "running_linked", + ], + ["blocking_reason", "estimated_reclaim_bytes"], + path, + ); + return { + target_id: boundedString(item.target_id, `${path}.target_id`), + action: targetKind(item.action, `${path}.action`), + worker_id: boundedString(item.worker_id, `${path}.worker_id`), + runtime_worker_id: boundedString(item.runtime_worker_id, `${path}.runtime_worker_id`), + runtime_id: boundedString(item.runtime_id, `${path}.runtime_id`), + reason: boundedString(item.reason, `${path}.reason`, true), + blocking_reason: optionalNullableString(item.blocking_reason, `${path}.blocking_reason`), + pinned: boolean(item.pinned, `${path}.pinned`), + retention_state: boundedString(item.retention_state, `${path}.retention_state`), + linked_workdir_ids: strings(item.linked_workdir_ids, `${path}.linked_workdir_ids`), + running_linked: boolean(item.running_linked, `${path}.running_linked`), + estimated_reclaim_bytes: optionalNullableSafeInteger( + item.estimated_reclaim_bytes, + `${path}.estimated_reclaim_bytes`, + ), + }; +} + +function workdirCandidate(value: unknown, path: string): CleanupWorkdirCandidate { + const item = object(value, path); + exactKeys( + item, + [ + "target_id", + "action", + "workdir_id", + "runtime_id", + "repository_key", + "reason", + "linked_worker_ids", + "linked_running_worker_ids", + "running_linked", + "pinned_linked", + "file_status", + "cleanliness", + ], + ["blocking_reason", "estimated_reclaim_bytes"], + path, + ); + const fileStatus = boundedString(item.file_status, `${path}.file_status`); + const cleanliness = boundedString(item.cleanliness, `${path}.cleanliness`); + if (!FILE_STATUSES.has(fileStatus as CleanupWorkdirFileStatus)) { + fail(`${path}.file_status`, "has an unknown value"); + } + if (!CLEANLINESS.has(cleanliness as CleanupWorkdirCleanliness)) { + fail(`${path}.cleanliness`, "has an unknown value"); + } + return { + target_id: boundedString(item.target_id, `${path}.target_id`), + action: targetKind(item.action, `${path}.action`), + workdir_id: boundedString(item.workdir_id, `${path}.workdir_id`), + runtime_id: boundedString(item.runtime_id, `${path}.runtime_id`), + repository_key: boundedString(item.repository_key, `${path}.repository_key`), + reason: boundedString(item.reason, `${path}.reason`, true), + blocking_reason: optionalNullableString(item.blocking_reason, `${path}.blocking_reason`), + linked_worker_ids: strings(item.linked_worker_ids, `${path}.linked_worker_ids`), + linked_running_worker_ids: strings( + item.linked_running_worker_ids, + `${path}.linked_running_worker_ids`, + ), + running_linked: boolean(item.running_linked, `${path}.running_linked`), + pinned_linked: boolean(item.pinned_linked, `${path}.pinned_linked`), + file_status: fileStatus as CleanupWorkdirFileStatus, + cleanliness: cleanliness as CleanupWorkdirCleanliness, + estimated_reclaim_bytes: optionalNullableSafeInteger( + item.estimated_reclaim_bytes, + `${path}.estimated_reclaim_bytes`, + ), + }; +} + +export function parseWorkerRetentionResponse(value: unknown): WorkerRetentionResponse { + const item = object(value, "Worker retention response"); + exactKeys( + item, + ["workspace_id", "runtime_id", "worker_id", "pinned", "retention_state"], + [], + "Worker retention response", + ); + return { + workspace_id: boundedString(item.workspace_id, "Worker retention response.workspace_id"), + runtime_id: boundedString(item.runtime_id, "Worker retention response.runtime_id"), + worker_id: boundedString(item.worker_id, "Worker retention response.worker_id"), + pinned: boolean(item.pinned, "Worker retention response.pinned"), + retention_state: boundedString( + item.retention_state, + "Worker retention response.retention_state", + ), + }; +} + +const WORKER_OPERATION_STATES = new Set([ + "accepted", + "unsupported", + "rejected", +]); + +export function parseRuntimeWorkerLifecycleResult( + value: unknown, +): RuntimeWorkerLifecycleResult { + const item = object(value, "Runtime Worker lifecycle result"); + exactKeys( + item, + ["state", "runtime_id", "worker_id", "diagnostics"], + [], + "Runtime Worker lifecycle result", + ); + const state = boundedString(item.state, "Runtime Worker lifecycle result.state"); + if (!WORKER_OPERATION_STATES.has(state as WorkerOperationState)) { + fail("Runtime Worker lifecycle result.state", "has an unknown value"); + } + return { + state: state as WorkerOperationState, + runtime_id: boundedString( + item.runtime_id, + "Runtime Worker lifecycle result.runtime_id", + ), + worker_id: boundedString( + item.worker_id, + "Runtime Worker lifecycle result.worker_id", + ), + diagnostics: diagnostics( + item.diagnostics, + "Runtime Worker lifecycle result.diagnostics", + ), + }; +} + +export function parseRuntimeCleanupPlan(value: unknown): RuntimeCleanupPlanResponse { + const item = object(value, "Runtime cleanup plan"); + exactKeys( + item, + [ + "workspace_id", + "runtime_id", + "generated_at", + "revision", + "digest", + "workers", + "workdirs", + "diagnostics", + ], + [], + "Runtime cleanup plan", + ); + return { + workspace_id: boundedString(item.workspace_id, "Runtime cleanup plan.workspace_id"), + runtime_id: boundedString(item.runtime_id, "Runtime cleanup plan.runtime_id"), + generated_at: boundedString(item.generated_at, "Runtime cleanup plan.generated_at"), + revision: boundedString(item.revision, "Runtime cleanup plan.revision"), + digest: boundedString(item.digest, "Runtime cleanup plan.digest"), + workers: array(item.workers, "Runtime cleanup plan.workers", MAX_CANDIDATES).map( + (entry, index) => workerCandidate(entry, `Runtime cleanup plan.workers[${index}]`), + ), + workdirs: array(item.workdirs, "Runtime cleanup plan.workdirs", MAX_CANDIDATES).map( + (entry, index) => workdirCandidate(entry, `Runtime cleanup plan.workdirs[${index}]`), + ), + diagnostics: diagnostics(item.diagnostics, "Runtime cleanup plan.diagnostics"), + }; +} + +function executionResult(value: unknown, path: string): RuntimeCleanupExecutionResult { + const item = object(value, path); + exactKeys(item, ["target_id", "action", "status", "message"], [], path); + return { + target_id: boundedString(item.target_id, `${path}.target_id`), + action: targetKind(item.action, `${path}.action`), + status: boundedString(item.status, `${path}.status`), + message: boundedString(item.message, `${path}.message`, true), + }; +} + +export function parseRuntimeCleanupExecution( + value: unknown, +): RuntimeCleanupExecutionResponse { + const item = object(value, "Runtime cleanup execution"); + exactKeys( + item, + ["workspace_id", "runtime_id", "executed_at", "results", "plan_after", "diagnostics"], + [], + "Runtime cleanup execution", + ); + return { + workspace_id: boundedString(item.workspace_id, "Runtime cleanup execution.workspace_id"), + runtime_id: boundedString(item.runtime_id, "Runtime cleanup execution.runtime_id"), + executed_at: boundedString(item.executed_at, "Runtime cleanup execution.executed_at"), + results: array(item.results, "Runtime cleanup execution.results", MAX_RESULTS).map( + (entry, index) => executionResult(entry, `Runtime cleanup execution.results[${index}]`), + ), + plan_after: parseRuntimeCleanupPlan(item.plan_after), + diagnostics: diagnostics(item.diagnostics, "Runtime cleanup execution.diagnostics"), + }; +} diff --git a/web/workspace/src/lib/workspace/console/composer-attachments.ts b/web/workspace/src/lib/workspace/console/composer-attachments.ts index 45d179a9a..15737998c 100644 --- a/web/workspace/src/lib/workspace/console/composer-attachments.ts +++ b/web/workspace/src/lib/workspace/console/composer-attachments.ts @@ -1,3 +1,4 @@ +import type { AttachmentUploadGrantResponse } from "$lib/generated/runtime-api.ts"; import type { UploadedFileRef } from "$lib/generated/protocol.ts"; export const MAX_UPLOADED_FILE_BYTES = 10 * 1024 * 1024; @@ -133,10 +134,19 @@ export function uploadAttachment( function isUploadGrantResponse( value: unknown, -): value is { upload_id: string; expires_at_ms: number } { - return !!value && typeof value === "object" && - "upload_id" in value && typeof value.upload_id === "string" && - "expires_at_ms" in value && typeof value.expires_at_ms === "number"; +): value is AttachmentUploadGrantResponse { + if (!value || typeof value !== "object" || Array.isArray(value)) return false; + const keys = Object.keys(value); + if ( + keys.length !== 2 || !keys.includes("upload_id") || + !keys.includes("expires_at_ms") + ) { + return false; + } + const grant = value as Record; + return typeof grant.upload_id === "string" && grant.upload_id.length > 0 && + grant.upload_id.length <= 256 && Number.isSafeInteger(grant.expires_at_ms) && + (grant.expires_at_ms as number) >= 0; } function isUploadedFileResponse( diff --git a/web/workspace/src/lib/workspace/repositories/ssh-connection.ts b/web/workspace/src/lib/workspace/repositories/ssh-connection.ts index 7b01e33f0..67e29f0e8 100644 --- a/web/workspace/src/lib/workspace/repositories/ssh-connection.ts +++ b/web/workspace/src/lib/workspace/repositories/ssh-connection.ts @@ -1,4 +1,4 @@ -import type { WorkspaceRuntimeResource } from "$lib/generated/legacy-server-api"; +import type { WorkspaceRuntimeResource } from "$lib/generated/runtime-api"; export type RepositorySshProbeSelection = Readonly<{ changed: boolean; diff --git a/web/workspace/src/lib/workspace/sidebar/types.ts b/web/workspace/src/lib/workspace/sidebar/types.ts index bf33a3742..db44802f9 100644 --- a/web/workspace/src/lib/workspace/sidebar/types.ts +++ b/web/workspace/src/lib/workspace/sidebar/types.ts @@ -1,3 +1,10 @@ +import type { + CleanupTargetKind, + CleanupWorkdirCandidate, + CleanupWorkerCandidate, + RuntimeCleanupExecutionResponse, + RuntimeCleanupPlanResponse, +} from "$lib/generated/runtime-api"; import type { BrowserCreateWorkerResponse as SharedBrowserCreateWorkerResponse, BrowserWorkerWorkingDirectorySelection @@ -123,67 +130,12 @@ export type WorkerLaunchProfileCandidate = SharedWorkerLaunchProfileCandidate; export type WorkingDirectoryRepositoryOption = SharedWorkingDirectoryRepositoryOption; -export type CleanupTargetKind = - | "worker_delete" - | "workdir_clean_cleanup" - | "workdir_dirty_discard" - | "workdir_record_delete"; - -export type CleanupWorkerCandidate = { - target_id: string; - action: CleanupTargetKind; - worker_id: string; - runtime_worker_id: string; - runtime_id: string; - reason: string; - blocking_reason?: string | null; - pinned: boolean; - retention_state: string; - linked_workdir_ids: string[]; - running_linked: boolean; - estimated_reclaim_bytes?: number | null; -}; - -export type CleanupWorkdirCandidate = { - target_id: string; - action: CleanupTargetKind; - workdir_id: string; - runtime_id: string; - repository_key: string; - reason: string; - blocking_reason?: string | null; - linked_worker_ids: string[]; - linked_running_worker_ids: string[]; - running_linked: boolean; - pinned_linked: boolean; - file_status: string; - cleanliness: string; - estimated_reclaim_bytes?: number | null; -}; - -export type RuntimeCleanupPlanResponse = { - workspace_id: string; - runtime_id: string; - generated_at: string; - revision: string; - digest: string; - workers: CleanupWorkerCandidate[]; - workdirs: CleanupWorkdirCandidate[]; - diagnostics: Diagnostic[]; -}; - -export type RuntimeCleanupExecutionResponse = { - workspace_id: string; - runtime_id: string; - executed_at: string; - results: { - target_id: string; - action: CleanupTargetKind; - status: string; - message: string; - }[]; - plan_after: RuntimeCleanupPlanResponse; - diagnostics: Diagnostic[]; +export type { + CleanupTargetKind, + CleanupWorkdirCandidate, + CleanupWorkerCandidate, + RuntimeCleanupExecutionResponse, + RuntimeCleanupPlanResponse, }; export type BrowserWorkerWorkingDirectorySelection = diff --git a/web/workspace/src/lib/workspace/sidebar/worker-actions.ts b/web/workspace/src/lib/workspace/sidebar/worker-actions.ts index c76d41f6a..2ac7adc89 100644 --- a/web/workspace/src/lib/workspace/sidebar/worker-actions.ts +++ b/web/workspace/src/lib/workspace/sidebar/worker-actions.ts @@ -1,19 +1,17 @@ import { workspaceApiPath } from "$lib/workspace/api/http"; +import { + parseRuntimeCleanupExecution, + parseRuntimeCleanupPlan, + parseRuntimeWorkerLifecycleResult, +} from "$lib/workspace/api/runtime-workers"; import type { Diagnostic, - RuntimeCleanupExecutionResponse, - RuntimeCleanupPlanResponse, Worker, } from "./types"; type FetchFn = typeof fetch; type WorkerActionTarget = Pick; -type WorkerLifecycleResponse = { - state: string; - diagnostics?: Diagnostic[]; -}; - function workerPath(workspaceId: string, worker: WorkerActionTarget): string { return workspaceApiPath( workspaceId, @@ -62,7 +60,7 @@ export async function stopSidebarWorker( }); if (!response.ok) throw new Error(await responseError(response)); - const result = await response.json() as WorkerLifecycleResponse; + const result = parseRuntimeWorkerLifecycleResult(await response.json()); if (result.state !== "accepted") { throw new Error( diagnosticMessage(result.diagnostics, `Worker stop was ${result.state}`), @@ -81,7 +79,7 @@ export async function deleteSidebarWorker( ); if (!planResponse.ok) throw new Error(await responseError(planResponse)); - const plan = await planResponse.json() as RuntimeCleanupPlanResponse; + const plan = parseRuntimeCleanupPlan(await planResponse.json()); const candidate = plan.workers.find((item) => item.runtime_id === worker.runtime_id && item.runtime_worker_id === worker.worker_id @@ -107,8 +105,7 @@ export async function deleteSidebarWorker( throw new Error(await responseError(executionResponse)); } - const execution = await executionResponse - .json() as RuntimeCleanupExecutionResponse; + const execution = parseRuntimeCleanupExecution(await executionResponse.json()); const outcome = execution.results.find((result) => result.target_id === candidate.target_id ); diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/+page.svelte index cfcd96a00..66114c0c1 100644 --- a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/+page.svelte @@ -4,7 +4,7 @@ RuntimeConnectionTestResponse, RuntimePublicIdentityBundle, WorkspaceRuntimeResource, - } from '$lib/generated/legacy-server-api'; + } from '$lib/generated/runtime-api'; import { createRemoteRuntime, previewRuntimePublicKeyFingerprint, @@ -397,12 +397,12 @@ {@const currentResult = currentTestResult(runtime)} - {#if runtime.diagnostics.length > 0 || currentResult} + {#if (runtime.diagnostics ?? []).length > 0 || currentResult} - {#if runtime.diagnostics.length > 0} + {#if (runtime.diagnostics ?? []).length > 0}
    - {#each runtime.diagnostics as diagnostic} + {#each runtime.diagnostics ?? [] as diagnostic}
  • {diagnostic.code} {diagnostic.message} @@ -413,8 +413,8 @@ {#if currentResult}
    Connection test: {connectionTestSummary(currentResult)} - {#if currentResult.diagnostics[0]} - {currentResult.diagnostics[0].message} + {#if (currentResult.diagnostics ?? [])[0]} + {(currentResult.diagnostics ?? [])[0].message} {/if} Checked {new Date(currentResult.checked_at).toLocaleString()}
    diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/+page.svelte index f3472859a..6e900f9c5 100644 --- a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/+page.svelte @@ -3,7 +3,7 @@ import type { RevokeRuntimeTrustKeyRequest, RuntimeTrustKeyStatus, - } from '$lib/generated/legacy-server-api'; + } from '$lib/generated/runtime-api'; import { createRemoteRuntime, removeRemoteRuntime, @@ -414,9 +414,9 @@
    Updated
    {formatTimestamp(trust.updated_at)}
    Revoked
    {formatTimestamp(trust.revoked_at)}
    - {#if runtime.diagnostics.length > 0} + {#if (runtime.diagnostics ?? []).length > 0}
      - {#each runtime.diagnostics as diagnostic} + {#each runtime.diagnostics ?? [] as diagnostic}
    • {diagnostic.code} {diagnostic.message} diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/workdirs/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/workdirs/+page.svelte index 74b732fda..d75721336 100644 --- a/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/workdirs/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/settings/runtimes/[runtimeId]/workdirs/+page.svelte @@ -1,10 +1,10 @@