mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 08:05:44 +00:00
feat(pipelines): schema contracts — save-time consumer checks vs captured schemas (#9917)
* feat(pipelines): schema contracts — save-time consumer checks vs captured schemas Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: move schemaContractContext above schemaCanEvolve doc comment Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: emit scd2/on_schema_change in CLI local graph, address review notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: gate editor _current ignore-suppression on scd2, matching backend Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5d7fb6deca
commit
42e11c6570
@@ -141,6 +141,8 @@ pub fn workspaced_service() -> Router {
|
||||
// CI test results
|
||||
.route("/ci_test_results/{kind}/{*path}", get(get_ci_test_results))
|
||||
.route("/ci_test_results_batch", post(get_ci_test_results_batch))
|
||||
// Save-time schema-contract check (pipelines gap #2b)
|
||||
.route("/check_schema_contracts", post(check_schema_contracts))
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -1305,6 +1307,14 @@ async fn create_script_internal<'c>(
|
||||
ns.path
|
||||
);
|
||||
}
|
||||
// `manual` materialize never captures a schema (no wrap codegen), so
|
||||
// there is no contract for `on_schema_change` to mute downstream.
|
||||
if m.manual && m.on_schema_change == windmill_parser::asset_parser::OnSchemaChange::Ignore {
|
||||
tracing::warn!(
|
||||
"script {}: `on_schema_change=ignore` on a `manual` materialize is inert — manual mode captures no schema, so consumers have no contract to check",
|
||||
ns.path
|
||||
);
|
||||
}
|
||||
}
|
||||
// `// macros` — this script is a workspace macro library: its body is
|
||||
// CREATE [OR REPLACE] MACRO statements plus plain setup, registered into
|
||||
@@ -3966,3 +3976,46 @@ async fn get_ci_test_results_batch(
|
||||
|
||||
Ok(Json(result_map))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CheckSchemaContractsRequest {
|
||||
language: ScriptLang,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CheckSchemaContractsResponse {
|
||||
warnings: Vec<windmill_common::schema_contracts::ContractWarning>,
|
||||
}
|
||||
|
||||
// Save-time schema-contract check (pipelines gap #2b): validate the given
|
||||
// script content's asset references (body column reads, `// column` lineage,
|
||||
// `// data_test relationships`) against the latest captured producer schemas
|
||||
// and return WARNINGS — never errors, and deploy never blocks on this. The
|
||||
// frontend calls it right after a successful deploy (post-commit, so a
|
||||
// self-produced target resolves to the fresh content) and the editor mirrors
|
||||
// the same diff client-side; this endpoint is the authoritative check. Parsing
|
||||
// uses the same server path as deploy (`effective_script_assets` +
|
||||
// `parse_pipeline_annotations`) so the verdict matches what deployed.
|
||||
async fn check_schema_contracts(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(req): Json<CheckSchemaContractsRequest>,
|
||||
) -> JsonResult<CheckSchemaContractsResponse> {
|
||||
let assets = crate::asset_inference::effective_script_assets(&req.language, &req.content, None)
|
||||
.unwrap_or_default();
|
||||
let ann = parse_pipeline_annotations(&req.content);
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let warnings = windmill_common::schema_contracts::check_schema_contracts(
|
||||
&mut tx,
|
||||
&w_id,
|
||||
&assets,
|
||||
&ann.column_lineage,
|
||||
&ann.data_tests,
|
||||
ann.materialize.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(CheckSchemaContractsResponse { warnings }))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user