Merge branch 'work/00001KY8KRJKK-virtual-config-tree' into develop

This commit is contained in:
2026-08-14 03:20:59 +09:00
45 changed files with 3875 additions and 36 deletions
+137 -3
View File
@@ -12,6 +12,7 @@ use axum::response::{IntoResponse, Response};
use axum::routing::{delete, get, patch, post, put};
use axum::{Json, Router};
use chrono::{Duration, SecondsFormat, Utc};
use config_source::ConfigTreeSnapshot;
use flow::{FlowSourceKind, FlowSourceResolveRequest, ResolvedFlowSource};
use futures::{SinkExt, StreamExt};
use memory::backend::{
@@ -61,6 +62,7 @@ use crate::companion::{
CompanionStatusResponse, CompanionTranscriptProjection,
};
use crate::config::{BackendRuntimesConfigFile, RemoteRuntimeConfigFile, resolve_remote_runtime};
use crate::config_source::{ConfigCommitRequest, ConfigPreviewRequest};
use crate::hosts::{
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EMBEDDED_RUNTIME_ID,
EmbeddedWorkerRuntime, HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime,
@@ -252,6 +254,7 @@ const ORCHESTRATOR_ATTENTION_PROMPT: &str = include_str!(concat!(
pub struct WorkspaceApi {
pub(crate) config: ServerConfig,
pub(crate) store: Arc<dyn ControlPlaneStore>,
config_store: Arc<crate::SqliteWorkspaceStore>,
authority: SqliteWorkspaceAuthority,
runtime: Arc<RuntimeRegistry>,
companion: Arc<CompanionConsole>,
@@ -741,7 +744,11 @@ impl WorkspaceApi {
let runtime = Arc::new(runtime);
let companion = Arc::new(CompanionConsole::disabled());
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
let config_store = Arc::new(crate::SqliteWorkspaceStore::open(
config.database_path.clone(),
)?);
let api = Self {
config_store,
authority: SqliteWorkspaceAuthority::new(
config.database_path.clone(),
config.workspace_id.clone(),
@@ -1132,6 +1139,26 @@ pub fn build_router(api: WorkspaceApi) -> Router {
"/api/w/{workspace_id}/settings/workspace",
get(scoped_get_workspace_settings).put(scoped_update_workspace_settings),
)
.route(
"/api/w/{workspace_id}/config/source-tree",
get(scoped_get_workspace_config_tree),
)
.route(
"/api/w/{workspace_id}/config/source-tree/preview",
post(scoped_preview_workspace_config_tree),
)
.route(
"/api/w/{workspace_id}/config/source-tree/commit",
post(scoped_commit_workspace_config_tree),
)
.route(
"/api/w/{workspace_id}/config/source-tree/revisions/{revision}",
get(scoped_get_workspace_config_revision),
)
.route(
"/api/w/{workspace_id}/config/source-tree/entries/{*path}",
get(scoped_get_workspace_config_entry),
)
.route(
"/api/w/{workspace_id}/settings/profiles",
get(scoped_get_profile_settings).post(scoped_create_profile_source),
@@ -2418,6 +2445,113 @@ async fn scoped_update_workspace_settings(
))
}
#[derive(Debug, Deserialize)]
struct WorkspaceConfigRevisionPath {
workspace_id: String,
revision: u64,
}
async fn scoped_get_workspace_config_revision(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<WorkspaceConfigRevisionPath>,
) -> ApiResult<Json<ConfigTreeSnapshot>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let snapshot = api
.config_store
.load_workspace_config_revision(&path.workspace_id, path.revision)?
.ok_or_else(|| ApiError::from(Error::InvalidRecordId(path.revision.to_string())))?;
Ok(Json(snapshot))
}
#[derive(Debug, Deserialize)]
struct WorkspaceConfigEntryPath {
workspace_id: String,
path: String,
}
#[derive(Debug, Serialize)]
struct WorkspaceConfigTreeResponse {
snapshot: ConfigTreeSnapshot,
contract: config_source::ToolchainContract,
projection_digest: String,
}
async fn scoped_get_workspace_config_tree(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
) -> ApiResult<Json<WorkspaceConfigTreeResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let state = api
.config_store
.load_workspace_config(&path.workspace_id)?
.unwrap_or_else(|| crate::config_source::WorkspaceConfigState {
snapshot: ConfigTreeSnapshot::empty(),
contract: config_source::ToolchainContract::new(
config_source::DEFAULT_SCHEMA_VERSION,
Vec::new(),
config_source::DEFAULT_IMPORT_POLICY_VERSION,
),
projection_digest: config_source::digest_bytes(b"[]"),
});
Ok(Json(WorkspaceConfigTreeResponse {
snapshot: state.snapshot,
contract: state.contract,
projection_digest: state.projection_digest,
}))
}
async fn scoped_get_workspace_config_entry(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<WorkspaceConfigEntryPath>,
) -> ApiResult<Json<config_source::ConfigEntry>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let virtual_path = config_source::VirtualPath::parse(&path.path)
.map_err(|error| ApiError::from(Error::InvalidInput(error.to_string())))?;
let state = api
.config_store
.load_workspace_config(&path.workspace_id)?
.ok_or_else(|| {
ApiError::from(Error::InvalidRecordId("virtual config source tree".into()))
})?;
let entry = state
.snapshot
.get(&virtual_path)
.cloned()
.ok_or_else(|| ApiError::from(Error::InvalidRecordId(path.path)))?;
Ok(Json(entry))
}
async fn scoped_preview_workspace_config_tree(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
Json(request): Json<ConfigPreviewRequest>,
) -> ApiResult<Json<crate::config_source::EvaluatedConfigCandidate>> {
validate_workspace_scope(&api, &path.workspace_id)?;
Ok(Json(
api.config_store
.preview_workspace_config(&path.workspace_id, &request)?,
))
}
async fn scoped_commit_workspace_config_tree(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
Json(request): Json<ConfigCommitRequest>,
) -> ApiResult<(StatusCode, Json<WorkspaceConfigTreeResponse>)> {
validate_workspace_scope(&api, &path.workspace_id)?;
let state = api
.config_store
.evaluate_and_commit_workspace_config(&path.workspace_id, &request)?;
Ok((
StatusCode::CREATED,
Json(WorkspaceConfigTreeResponse {
snapshot: state.snapshot,
contract: state.contract,
projection_digest: state.projection_digest,
}),
))
}
async fn scoped_get_profile_settings(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
@@ -11273,9 +11407,9 @@ impl IntoResponse for ApiError {
Error::BrowserMergeConfirmationRequired | Error::BrowserReopenConfirmationRequired => {
StatusCode::FORBIDDEN
}
Error::TicketAssignmentConflict(_) | Error::WorkdirAttachmentConflict(_) => {
StatusCode::CONFLICT
}
Error::TicketAssignmentConflict(_)
| Error::WorkdirAttachmentConflict(_)
| Error::WorkspaceConfigConflict(_) => StatusCode::CONFLICT,
Error::WorkerSourceIdentity(_) | Error::InvalidInput(_) => StatusCode::BAD_REQUEST,
Error::InvalidRuntimeIdentifier { .. } | Error::ReservedWorkerName(_) => {
StatusCode::BAD_REQUEST