From 6cbc3a698867d96704adae24f78368be411a3137 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 23 Sep 2026 04:27:52 +0900 Subject: [PATCH] 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 }),