feat: migrate workspace configuration domains to ServerApi
This commit is contained in:
Generated
+1
@@ -4149,6 +4149,7 @@ name = "server-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"api-macros",
|
||||
"memory",
|
||||
"protocol",
|
||||
"runtime-api",
|
||||
"schemars",
|
||||
|
||||
@@ -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<server_api::MemoryDocumentResponse, BackendWorkspaceClientError> {
|
||||
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<server_api::MemoryStagingListResponse, BackendWorkspaceClientError> {
|
||||
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<Vec<BackendWorkspace>, BackendWorkspaceClientError> {
|
||||
|
||||
@@ -310,14 +310,14 @@ impl BackendWorkspaceProductClient {
|
||||
}
|
||||
|
||||
pub fn memory_document(&self) -> Result<MemoryDocumentResponse, BackendWorkspaceClientError> {
|
||||
self.get_json("/memory")
|
||||
crate::backend_workspace::memory_document_blocking(&self.api, &self.workspace_id)
|
||||
}
|
||||
|
||||
pub fn list_memory_staging(
|
||||
&self,
|
||||
limit: usize,
|
||||
) -> Result<MemoryStagingListResponse, BackendWorkspaceClientError> {
|
||||
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()
|
||||
|
||||
+38
-15
@@ -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<String>,
|
||||
@@ -95,31 +96,39 @@ pub struct ModelAudit {
|
||||
pub model_id: Option<String>,
|
||||
}
|
||||
|
||||
#[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<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))]
|
||||
pub output_tokens: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 0, max = 9_007_199_254_740_991_u64))]
|
||||
pub total_tokens: Option<u64>,
|
||||
#[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<u64>,
|
||||
#[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<u64>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub segment_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<crate::schema::JsonSafeU64Pair>")]
|
||||
pub entry_range: Option<[u64; 2]>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<crate::schema::JsonSafeU64Pair>")]
|
||||
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<String>,
|
||||
@@ -127,13 +136,16 @@ pub struct ExtractAudit {
|
||||
pub staging_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
@@ -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<ConsolidationAudit>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))]
|
||||
pub result_count: Option<usize>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[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<Utc>,
|
||||
#[serde(flatten)]
|
||||
pub payload: AuditPayload,
|
||||
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
@@ -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<usize>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 0, max = 9_007_199_254_740_991_usize))]
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -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<SourceEvidenceRef>,
|
||||
}
|
||||
|
||||
#[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<usize>,
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
@@ -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<crate::schema::JsonSafeU64Pair>")]
|
||||
pub entry_range: Option<[u64; 2]>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub origin: Option<EvidenceOrigin>,
|
||||
|
||||
@@ -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<String>,
|
||||
#[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<u64>,
|
||||
}
|
||||
|
||||
@@ -118,6 +128,7 @@ pub struct SourceEvidenceRef {
|
||||
pub segment_id: Option<String>,
|
||||
/// `[start_entry, end_entry]` inclusive range of session-store entry indices.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<crate::schema::JsonSafeU64Pair>")]
|
||||
pub entry_range: Option<[u64; 2]>,
|
||||
/// Host-assigned evidence id within the referenced evidence set.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+1197
-52
File diff suppressed because it is too large
Load Diff
@@ -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)?;
|
||||
|
||||
@@ -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::<MemoryBackendHttpResponse>(&response.body)? {
|
||||
match serde_json::from_str::<server_api::MemoryBackendResponse>(&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::<MemoryConsolidationOutput>(&response.body).map_err(Into::into)
|
||||
let response = serde_json::from_str::<server_api::MemoryConsolidationResponse>(&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<dyn WorkspaceClient>) -> Vec<ToolDefinition> {
|
||||
|
||||
@@ -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<SkillDiagnostic>,
|
||||
/// 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}")]
|
||||
|
||||
@@ -3889,8 +3889,8 @@ impl<C: LlmClient + 'static, St: Store> Worker<C, St> {
|
||||
.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<C: LlmClient + 'static, St: Store> Worker<C, St> {
|
||||
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()))?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+7243
-1
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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<WorkspaceSummary>;
|
||||
|
||||
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,
|
||||
/**
|
||||
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<WorkspaceDeletionBlocker>, };
|
||||
expected_revision: string;
|
||||
can_delete: boolean;
|
||||
resources: WorkspaceDeletionResourceCounts;
|
||||
blockers: Array<WorkspaceDeletionBlocker>;
|
||||
};
|
||||
|
||||
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<string>, blockers: Array<WorkspaceDeletionBlocker>, 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<string>;
|
||||
blockers: Array<WorkspaceDeletionBlocker>;
|
||||
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<Diagnostic>, };
|
||||
export type WorkspaceExtensionPointState = {
|
||||
status: string;
|
||||
note: string;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<Diagnostic>, };
|
||||
export type WorkspaceMetadataSettingsResponse = {
|
||||
workspace_id: string;
|
||||
display_name: string;
|
||||
created_at: string;
|
||||
revision: string;
|
||||
source: string;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
export type UpdateWorkspaceMetadataRequest = { display_name: string, revision: string, };
|
||||
export type UpdateWorkspaceMetadataRequest = {
|
||||
display_name: string;
|
||||
revision: string;
|
||||
};
|
||||
|
||||
export type WorkspaceMetadataMutationResponse = { workspace: WorkspaceMetadataSettingsResponse, diagnostics: Array<Diagnostic>, };
|
||||
export type WorkspaceMetadataMutationResponse = {
|
||||
workspace: WorkspaceMetadataSettingsResponse;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<WorkspaceProfileSummary>, sources: Array<WorkspaceProfileSourceSummary>, diagnostics: Array<Diagnostic>, };
|
||||
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<Diagnostic>, };
|
||||
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<Diagnostic>, };
|
||||
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<ConfigSchemaContribution>;
|
||||
source: string;
|
||||
fingerprint: string;
|
||||
};
|
||||
|
||||
export type ToolchainContract = {
|
||||
contract_version: number;
|
||||
decodal_version: string;
|
||||
schema_version: number;
|
||||
entrypoints: Array<string>;
|
||||
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<ConfigTreeChange>;
|
||||
entrypoints: Array<string>;
|
||||
};
|
||||
|
||||
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<WorkspaceProfileSummary>;
|
||||
sources: Array<WorkspaceProfileSourceSummary>;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<Diagnostic>;
|
||||
};
|
||||
|
||||
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<Diagnostic>;
|
||||
};
|
||||
|
||||
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<string>, refs: Array<string>, };
|
||||
export type GitCommitSummary = {
|
||||
hash: string;
|
||||
short_hash: string;
|
||||
summary: string;
|
||||
author_name: string;
|
||||
author_email: string;
|
||||
author_date: string;
|
||||
parents: Array<string>;
|
||||
refs: Array<string>;
|
||||
};
|
||||
|
||||
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<RepositorySshHostKeyCandidate>, };
|
||||
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<RepositorySshHostKeyCandidate>;
|
||||
};
|
||||
|
||||
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<GitCommitSummary>, diagnostics: Array<Diagnostic>, };
|
||||
export type RepositoryLogResponse = {
|
||||
workspace_id: string;
|
||||
repository_key: string;
|
||||
default_selector?: string | null;
|
||||
limit: number;
|
||||
items: Array<GitCommitSummary>;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<string>, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array<Diagnostic>, };
|
||||
export type RuntimeSummary = {
|
||||
runtime_id: string;
|
||||
label: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
source: RuntimeSourceSummary;
|
||||
host_ids: Array<string>;
|
||||
worker_creation_available: boolean;
|
||||
os: string;
|
||||
arch: string;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<string>, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array<Diagnostic>, };
|
||||
export type WorkspaceRuntimeResource = {
|
||||
management: RuntimeManagementSummary;
|
||||
runtime_id: string;
|
||||
label: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
source: RuntimeSourceSummary;
|
||||
host_ids: Array<string>;
|
||||
worker_creation_available: boolean;
|
||||
os: string;
|
||||
arch: string;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
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<RuntimeTrustAuditEntry>, };
|
||||
export type WorkspaceRuntimeDetail = {
|
||||
workspace_id: string;
|
||||
runtime: WorkspaceRuntimeResource;
|
||||
endpoint?: string | null;
|
||||
trust_key: RuntimeTrustKeyState;
|
||||
recent_audit: Array<RuntimeTrustAuditEntry>;
|
||||
};
|
||||
|
||||
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<Diagnostic>, };
|
||||
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<Diagnostic>;
|
||||
};
|
||||
|
||||
@@ -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<MemoryStagingEvidence>, source_refs: Array<MemorySourceEvidenceRef>, };
|
||||
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<MemoryStagingEvidence>;
|
||||
source_refs: Array<MemorySourceEvidenceRef>;
|
||||
};
|
||||
|
||||
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<MemoryStagingEntry>, diagnostics: Array<Diagnostic>, };
|
||||
export type MemoryStagingListResponse = {
|
||||
limit: number;
|
||||
returned_count: number;
|
||||
total_valid_count: number;
|
||||
invalid_count: number;
|
||||
truncated: boolean;
|
||||
order: string;
|
||||
record_authority: string;
|
||||
items: Array<MemoryStagingEntry>;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
|
||||
@@ -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<string>, };
|
||||
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<string>;
|
||||
};
|
||||
|
||||
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<string>, };
|
||||
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<string>;
|
||||
};
|
||||
|
||||
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<RepositorySshAccessBinding>, };
|
||||
export type RepositoryAccessProjection = {
|
||||
workspace_id: string;
|
||||
config_revision: number;
|
||||
projection_digest: string;
|
||||
bindings: Array<RepositorySshAccessBinding>;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SkillProvenance>, diagnostics: Array<SkillDiagnostic>, };
|
||||
export type SkillCatalogEntry = {
|
||||
name: string;
|
||||
description: string;
|
||||
activation_status: SkillActivationStatus;
|
||||
projection_status: SkillProjectionStatus;
|
||||
provenance: SkillProvenance;
|
||||
overrides: Array<SkillProvenance>;
|
||||
diagnostics: Array<SkillDiagnostic>;
|
||||
};
|
||||
|
||||
export type SkillCatalogResponse = { authority: string, projection: SkillProjectionIdentity, entries: Array<SkillCatalogEntry>, diagnostics: Array<SkillDiagnostic>, };
|
||||
export type SkillCatalogResponse = {
|
||||
authority: string;
|
||||
projection: SkillProjectionIdentity;
|
||||
entries: Array<SkillCatalogEntry>;
|
||||
diagnostics: Array<SkillDiagnostic>;
|
||||
};
|
||||
|
||||
export type SkillDetailResponse = { authority: string, projection: SkillProjectionIdentity, name: string, description: string, provenance: SkillProvenance, overrides: Array<SkillProvenance>, diagnostics: Array<SkillDiagnostic>, activation_status: SkillActivationStatus, projection_status: SkillProjectionStatus, body: string, allowed_tools: Array<string>, allowed_tools_status: string, resources: Array<SkillResourceRef>, };
|
||||
export type SkillDetailResponse = {
|
||||
authority: string;
|
||||
projection: SkillProjectionIdentity;
|
||||
name: string;
|
||||
description: string;
|
||||
provenance: SkillProvenance;
|
||||
overrides: Array<SkillProvenance>;
|
||||
diagnostics: Array<SkillDiagnostic>;
|
||||
activation_status: SkillActivationStatus;
|
||||
projection_status: SkillProjectionStatus;
|
||||
body: string;
|
||||
allowed_tools: Array<string>;
|
||||
allowed_tools_status: string;
|
||||
resources: Array<SkillResourceRef>;
|
||||
};
|
||||
|
||||
export type SkillActivationResponse = {
|
||||
name: string;
|
||||
provenance: SkillProvenance;
|
||||
diagnostics: Array<SkillDiagnostic>;
|
||||
body: string;
|
||||
};
|
||||
|
||||
@@ -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<T>(
|
||||
fetcher: typeof fetch,
|
||||
@@ -27,7 +31,10 @@ export async function loadRepositoryAccessJson<T>(
|
||||
|
||||
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.",
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
|
||||
type Parser<T> = (value: unknown) => T;
|
||||
|
||||
function sourceTreeUrl(workspaceId: string): string {
|
||||
return `/api/w/${encodeURIComponent(workspaceId)}/config/source-tree`;
|
||||
}
|
||||
|
||||
async function readJson<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(body || `${response.status} ${response.statusText}`);
|
||||
async function readJson<T>(
|
||||
response: Response,
|
||||
parser: Parser<T>,
|
||||
): Promise<T> {
|
||||
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<string, string> {
|
||||
const item = objectValue(value);
|
||||
const entries = Object.entries(item);
|
||||
if (entries.length > MAX_COLLECTION_ITEMS) throw schemaError();
|
||||
const result: Record<string, string> = {};
|
||||
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<string, ConfigEntry> = {};
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<MemoryCandidateKind>([
|
||||
"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<string, unknown>, key: string): string {
|
||||
if (typeof record[key] !== "string") {
|
||||
invalid(`${key} must be a string`);
|
||||
function requiredString(
|
||||
record: Record<string, unknown>,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<T>(
|
||||
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<string> {
|
||||
const bytes = new Uint8Array(await response.arrayBuffer());
|
||||
if (bytes.byteLength > MAX_PROFILE_ERROR_BYTES) return "";
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
async function parseResponse<T>(
|
||||
response: Response,
|
||||
parser: (value: unknown) => T,
|
||||
): Promise<T> {
|
||||
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(
|
||||
|
||||
@@ -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<unknown>(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`,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
/// <reference lib="deno.ns" />
|
||||
|
||||
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 }),
|
||||
|
||||
Reference in New Issue
Block a user