feat(AF-624): pre-flight query cost & blast-radius estimation - #644
Merged
Conversation
Contributor
Frontend Test Results1 296 tests 1 296 ✅ 4m 42s ⏱️ Results for commit 93882cc. ♻️ This comment has been updated with latest results. |
Contributor
Coverage Report for Frontend Coverage (frontend)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Contributor
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #624
Summary
Automatically runs the engine's native cost estimation (EXPLAIN /
explain()) right after every query submission and — for UPDATE/DELETE — an actual affected-row count, then surfaces the result to reviewers, routing policies, and the AI analyzer's prompt.Builds on the AF-445 dry-run capability (
QueryEngine.dryRun/QueryExecutor.dryRun/ per-dialectDryRunPlanners) rather than adding a newExplainCapabilitySPI — the default-method pattern already covers "engines opt in individually; absent capability → estimate unavailable, never blocks submission".Backend
query_estimatestable (V127/V128, mirrorsai_analyses+ back-pointer): engine id, supported flag, estimated rows, exact affected-row count, scan type (root plan operation), cost, raw plan, failure sentinel columns.proxy.api.QueryCostEstimateService(DefaultQueryCostEstimateService) — idempotent, never-throws estimate pipeline: row-security-governeddryRun, plus for UPDATE/DELETE an exact count — relational via a JSqlParserSELECT COUNT(*)rewrite (AffectedRowCounter, RLS applied by the existingRowSecurityRewriter), engine-managed via the new default SPI methodQueryEngine.countAffectedRows(...)returningQueryAffectedRowsResult(sentinel-unsupported default).QueryCostEstimateListeneronQuerySubmittedEvent, and eagerly fromDefaultAiAnalyzerServicebefore the LLM call.ACCESSFLOW_PROXY_ESTIMATE_TIMEOUT(defaultPT5S).estimated_rows(comparison) andscan_type(glob) with a live, fail-closed lookup inQueryReviewStateMachine.buildContext— absent/unsupported/failed estimate ⇒ leaf is false.{{cost_estimate}}placeholder inSystemPromptRenderer.DEFAULT_TEMPLATE;costEstimateContextparameter threaded throughAiAnalyzerStrategyand all adapters/decorators.GET /queries/{id}gains a nullablecost_estimateobject; newquery.estimate_completeWebSocket event.Engine plugins (re-pinned in this PR)
countAffectedRowsoverrides for the four dry-run-capable engines, each with a version bump +connector.jsonSHA-256 re-pin:countDocumentswith the$match-injected RLS filterSELECT COUNT(*)rewrite of the WHERE-spliced statement (CouchbaseCountRewriter)count(...)RETURN-projection rewrite of MATCH…DELETE/SET (CypherAffectedCountDeriver)_countwith thebool.filter-wrapped RLS query (update_by_query/delete_by_query)Redis, Cassandra/ScyllaDB, DynamoDB, and the warehouse engines inherit the default and degrade gracefully (no bytecode change, no re-pin).
Frontend
CostEstimatePanel(components/review/) onQueryDetailPage— reusesPlanTree; states: pending (whilePENDING_AI), available (rows/count/scan type/cost + plan tree + raw plan), unavailable, failed.query.estimate_completeinvalidation inwebsocketManager;cost_estimatetypes intypes/api.ts; i18n keys in all 5 locales.routingPolicyForm.ts,RoutingPoliciesPage).e2e
Extended
e2e/tests/query-submit.spec.ts: after submittingUPDATE inventory.products …, the detail page shows the cost-estimate panel with plan + affected-row count (WS-driven, no reload).Docs & website updated
docs/03-data-model.md—query_estimatestable; routing condition wire-format rowsdocs/04-api-spec.md—cost_estimatein the detail response; WS event row; routing-policy condition listdocs/05-backend.md— new "Automatic pre-flight cost estimate (AF-624)" section; routing-engine + prompt-template updates; engine SPI tabledocs/06-frontend.md— QueryDetailPage panel bullet; WS invalidations tabledocs/09-deployment.md+CLAUDE.md—ACCESSFLOW_PROXY_ESTIMATE_TIMEOUT; updatedAiAnalyzerStrategysignature quotedocs/15-engine-sdk.md—countAffectedRowsSPI method rowREADME.md,website/index.html,website/docs/index.html— feature pitch + routing-policy condition docsVerification
mvn verify -Pcoverage: 5,428 tests green (incl.ApplicationModulesTest,ApiPackageDependencyTest)check-engine-pins.mjstest:coverage(90/80 gate) + build greenquery-submit,ws-realtime,admin-routing-policies,reviews-approve,query-executespecs green locally