config: commit without preview
This commit is contained in:
@@ -108,10 +108,8 @@ pub struct WorkspaceConfigState {
|
||||
pub projection_digest: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EvaluatedConfigCandidate {
|
||||
#[ts(type = "number")]
|
||||
pub base_revision: u64,
|
||||
pub base_digest: String,
|
||||
pub snapshot: ConfigTreeSnapshot,
|
||||
@@ -127,14 +125,6 @@ pub struct ConfigCommitRequest {
|
||||
pub base_digest: String,
|
||||
pub changes: Vec<ConfigTreeChange>,
|
||||
pub entrypoints: Vec<VirtualPath>,
|
||||
pub toolchain_fingerprint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct ConfigPreviewRequest {
|
||||
pub changes: Vec<ConfigTreeChange>,
|
||||
pub entrypoints: Vec<VirtualPath>,
|
||||
}
|
||||
|
||||
impl SqliteWorkspaceStore {
|
||||
@@ -245,13 +235,6 @@ impl SqliteWorkspaceStore {
|
||||
current.snapshot.revision
|
||||
)));
|
||||
}
|
||||
let expected_contract = main_config_contract_with_schema(schema_bundle.clone());
|
||||
if expected_contract.fingerprint != request.toolchain_fingerprint {
|
||||
return Err(config_conflict(format!(
|
||||
"toolchain fingerprint mismatch; current fingerprint is {}",
|
||||
expected_contract.fingerprint
|
||||
)));
|
||||
}
|
||||
evaluate_candidate(current, &request.changes, schema_bundle)
|
||||
}
|
||||
|
||||
@@ -267,31 +250,6 @@ impl SqliteWorkspaceStore {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn preview_workspace_config_with_schema(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
request: &ConfigPreviewRequest,
|
||||
schema_bundle: WorkspaceConfigSchemaBundle,
|
||||
) -> Result<EvaluatedConfigCandidate> {
|
||||
validate_entrypoint_request(&request.entrypoints)?;
|
||||
let current = self
|
||||
.load_workspace_config(workspace_id)?
|
||||
.ok_or_else(config_not_materialized)?;
|
||||
evaluate_candidate(current, &request.changes, schema_bundle)
|
||||
}
|
||||
|
||||
pub fn preview_workspace_config(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
request: &ConfigPreviewRequest,
|
||||
) -> Result<EvaluatedConfigCandidate> {
|
||||
self.preview_workspace_config_with_schema(
|
||||
workspace_id,
|
||||
request,
|
||||
WorkspaceConfigSchemaBundle::empty(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn commit_evaluated_workspace_config(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
@@ -943,7 +901,6 @@ mod tests {
|
||||
base_digest: current.snapshot.digest.clone(),
|
||||
changes,
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
toolchain_fingerprint: current.contract.fingerprint.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,7 +948,6 @@ mod tests {
|
||||
content: "{ web = {}; }".to_string(),
|
||||
}],
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
toolchain_fingerprint: expected_contract.fingerprint.clone(),
|
||||
},
|
||||
schema,
|
||||
)
|
||||
@@ -1018,34 +974,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn commit_rejects_stale_schema_bundle_fingerprint() {
|
||||
let store = open_store().await;
|
||||
let current = store.load_workspace_config("w-config").unwrap().unwrap();
|
||||
let schema = WorkspaceConfigSchemaBundle::compose([ConfigSchemaContribution::new(
|
||||
"builtin:web",
|
||||
"web",
|
||||
"1",
|
||||
"{ web = {}; }",
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
let error = store
|
||||
.evaluate_workspace_config_candidate_with_schema(
|
||||
"w-config",
|
||||
&ConfigCommitRequest {
|
||||
base_revision: current.snapshot.revision,
|
||||
base_digest: current.snapshot.digest,
|
||||
changes: Vec::new(),
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
toolchain_fingerprint: current.contract.fingerprint,
|
||||
},
|
||||
schema,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains("toolchain fingerprint mismatch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_state_evaluation_rejects_provider_fingerprint_drift() {
|
||||
let snapshot = ConfigTreeSnapshot::from_entries(
|
||||
@@ -1361,10 +1289,11 @@ mod tests {
|
||||
let store = open_store().await;
|
||||
let current = store.load_workspace_config("w-config").unwrap().unwrap();
|
||||
let candidate = store
|
||||
.preview_workspace_config(
|
||||
.evaluate_workspace_config_candidate(
|
||||
"w-config",
|
||||
&ConfigPreviewRequest {
|
||||
changes: vec![
|
||||
&commit_request(
|
||||
¤t,
|
||||
vec![
|
||||
update_main(¤t, "{}"),
|
||||
ConfigTreeChange::Create {
|
||||
path: path("module.dcdl"),
|
||||
@@ -1372,8 +1301,7 @@ mod tests {
|
||||
content: "{}".into(),
|
||||
},
|
||||
],
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
},
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1469,12 +1397,9 @@ mod tests {
|
||||
},
|
||||
] {
|
||||
let error = store
|
||||
.preview_workspace_config(
|
||||
.evaluate_workspace_config_candidate(
|
||||
"w-config",
|
||||
&ConfigPreviewRequest {
|
||||
changes: vec![change],
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
},
|
||||
&commit_request(¤t, vec![change]),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains("cannot be"));
|
||||
@@ -1484,14 +1409,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn browser_cannot_replace_server_owned_entrypoint_contract() {
|
||||
let store = open_store().await;
|
||||
let current = store.load_workspace_config("w-config").unwrap().unwrap();
|
||||
let mut request = commit_request(¤t, Vec::new());
|
||||
request.entrypoints = vec![path("other.dcdl")];
|
||||
let error = store
|
||||
.preview_workspace_config(
|
||||
"w-config",
|
||||
&ConfigPreviewRequest {
|
||||
changes: Vec::new(),
|
||||
entrypoints: vec![path("other.dcdl")],
|
||||
},
|
||||
)
|
||||
.evaluate_workspace_config_candidate("w-config", &request)
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains("must be exactly [main.dcdl]"));
|
||||
}
|
||||
@@ -1514,12 +1436,6 @@ mod tests {
|
||||
content: "{ broken = ; }".into(),
|
||||
}],
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
toolchain_fingerprint: ToolchainContract::new(
|
||||
DEFAULT_SCHEMA_VERSION,
|
||||
vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
DEFAULT_IMPORT_POLICY_VERSION,
|
||||
)
|
||||
.fingerprint,
|
||||
},
|
||||
)
|
||||
.unwrap_err();
|
||||
@@ -1587,7 +1503,6 @@ mod tests {
|
||||
content: "{ answer = 2; }".into(),
|
||||
}],
|
||||
entrypoints: first.contract.entrypoints.clone(),
|
||||
toolchain_fingerprint: first.contract.fingerprint.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1598,27 +1513,6 @@ mod tests {
|
||||
assert_eq!(revision, first.snapshot);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn commit_rejects_mismatched_toolchain_fingerprint() {
|
||||
let store = SqliteWorkspaceStore::in_memory().unwrap();
|
||||
store.upsert_workspace(&workspace()).await.unwrap();
|
||||
let current = store.load_workspace_config("w-config").unwrap().unwrap();
|
||||
let error = store
|
||||
.evaluate_and_commit_workspace_config(
|
||||
"w-config",
|
||||
&ConfigCommitRequest {
|
||||
base_revision: current.snapshot.revision,
|
||||
base_digest: current.snapshot.digest.clone(),
|
||||
changes: vec![update_main(¤t, "{ answer = 42; }")],
|
||||
entrypoints: vec![path(MAIN_CONFIG_ENTRYPOINT)],
|
||||
toolchain_fingerprint: "sha256:stale-toolchain".into(),
|
||||
},
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(error, Error::WorkspaceConfigConflict(_)));
|
||||
assert!(store.load_workspace_config("w-config").unwrap().is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn migration_materializes_main_for_existing_workspace_without_config() {
|
||||
let conn = rusqlite::Connection::open_in_memory().unwrap();
|
||||
@@ -1653,9 +1547,7 @@ mod tests {
|
||||
.join("../../web/workspace/src/lib/workspace/config-source/generated/types");
|
||||
let config = ts_rs::Config::default().with_out_dir(&output);
|
||||
WorkspaceConfigState::export_all(&config).unwrap();
|
||||
EvaluatedConfigCandidate::export_all(&config).unwrap();
|
||||
ConfigCommitRequest::export_all(&config).unwrap();
|
||||
ConfigPreviewRequest::export_all(&config).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -62,7 +62,7 @@ use crate::companion::{
|
||||
CompanionStatusResponse, CompanionTranscriptProjection,
|
||||
};
|
||||
use crate::config::{BackendRuntimesConfigFile, RemoteRuntimeConfigFile, resolve_remote_runtime};
|
||||
use crate::config_source::{ConfigCommitRequest, ConfigPreviewRequest};
|
||||
use crate::config_source::ConfigCommitRequest;
|
||||
use crate::hosts::{
|
||||
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EMBEDDED_RUNTIME_ID,
|
||||
EmbeddedWorkerRuntime, HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime,
|
||||
@@ -1156,10 +1156,6 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||
"/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),
|
||||
@@ -2514,21 +2510,6 @@ async fn scoped_get_workspace_config_entry(
|
||||
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)?;
|
||||
let candidate = api.config_store.preview_workspace_config_with_schema(
|
||||
&path.workspace_id,
|
||||
&request,
|
||||
api.config_schema_registry.compose()?,
|
||||
)?;
|
||||
crate::prompt_settings::validate_evaluated_prompt_catalog(&candidate.evaluation)?;
|
||||
Ok(Json(candidate))
|
||||
}
|
||||
|
||||
async fn scoped_commit_workspace_config_tree(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||
@@ -14114,7 +14095,6 @@ mod tests {
|
||||
},
|
||||
],
|
||||
entrypoints: current.contract.entrypoints.clone(),
|
||||
toolchain_fingerprint: current.contract.fingerprint.clone(),
|
||||
};
|
||||
let candidate = api
|
||||
.config_store
|
||||
|
||||
Reference in New Issue
Block a user