Repository navigation
feat(dag-viewer): plan through stage_pipeline in the live viewer; delete dag_export - #686
Merged
Merged
Conversation
… with --table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
server.py's /api/plan runs stage_pipeline (one language per plan; SQL tables via --table) and returns its asap-stage-pipeline/v1 document; stage-workload.js shows it as Stage 0 before and the Stage 3 selected plan after, with per-node costs. generate-sample.sh writes dag.example.json with stage_pipeline; render.py embeds a stage document unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
stage_pipeline and the viewer now cover the live path. CostUnit, the only asap_types::cost item still used, moves into asap-aware-mapping's cost_model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
stage_pipeline takes --promql and --sql in any order, each optionally named with --name; the Query planner sends every checked row with its name, and the sample is its default mixed workload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
Drop the old export format (pre/post-ASAP WorkloadDAG, translation cards, baseline/benefit and edge cost annotations, multi-file merge, render.py's IR labeling) and show Stage 3's candidate table from the stage document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
A workload whose Stage 1 Cartesian product exceeds --max-combinations (default 10^4) is refused before enumeration: exit status 3 with the count and the limit, no document written. Planning itself still ranks every candidate; only --max-displayed caps the display. The count comes from Pass 1 alone (planning_stages::combinations). Also reject a --table declared twice, and say when a duplicate query name is a query's default name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…checks server.py plans under a lock, checks field types (request object, query name/language/text, selected schemas, epsilon and interval_ms not booleans), says which query already uses a duplicate name, and returns stage_pipeline's combination-limit refusal as 422 with its message. The editor shows server errors without the start-with-server.py hint and says "query-language frontends". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion errors
stageCandidateRows lists Stage 1 candidates that did not compose
("composition error") with their errors, after the pruned ones.
stageCandidateHeader adds the Stage 3 selection error to the table
header. Lane labels say "query-language frontends".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When Pass 1 fails, stage_pipeline still writes the document: the Stage 0 DAG, the error under stage1_logical_asap_optimization.error, and no selection. The combination limit no longer re-runs Pass 1: the library splits run_stage1_to_stage3 at Pass 1 (run_stage1_to_stage3_from_pass1, PerComputationCandidates::combinations), and planning_stages checks the limit on that one inventory (PlanningStagesError). A --table time_index that is not a column index is an error rather than no time column. Adds examples/scalar-queries.json, a stage_pipeline document with standalone scalar queries first, in the middle and last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0 without a plan stageWorkload mapped query i to roots[i], but the physical DAG keeps standalone scalar queries in scalar_roots, so scalar(v), time() or 1 + 2 crashed the viewer. Operator queries now take the next operator root; a scalar query's root is a Scalar node showing its expression over the operator nodes it reads, in both the Stage 0 and the selected-plan view. The viewer shows the Stage 0 lanes when no plan was selected, and the candidate table header shows a Stage 1 (Pass 1) error. server.py checks column entries and time_index (400 rather than a stage_pipeline failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
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.
Why
The dag-viewer's live path (
server.py,generate-sample.sh, the editor's "Plan selected workload") still ran the legacydag_exportbinary. That binary drives the legacy candidate search, not the stage pipeline onmain(Stage 0 → Stage 3,run_stage1_to_stage3). Moving the viewer ontostage_pipelineleavesdag_export,asap_types::dag_exportandasap_types::costunused, so this PR deletes them.What
server.pyandgenerate-sample.shplan throughstage_pipelineand read onlyasap-stage-pipeline/v1documents.stage_pipelinegains what the editor needs fromdag_export:dag_exportcapability--promql,--epsilon,--data-ingestion-interval-ms)stage_pipeline --promql … --epsilon … --interval-ms …--sql … --table '<json>'(repeatable; no built-in tables)--promql/--sqlin any order, planned as one workload (run_planning_stages_over)--namesetsworkload.queries[].id(defaultq<n>)dag, still written when Pass 1 failsPhysicalASAPDAGwith Stage 3costs[id].per_nodecrates/devtools/src/bin/dag_export.rs,crates/types/src/dag_export.rs,crates/types/src/cost.rs,tools/dag-viewer/post_asap_fixture.json, and the WorkloadDAG reader, multi-file merge and IR labeling inviewer.js/render.py.CostUnitmoves toasap_aware_mapping::cost_model, with the same serde.--planner-cost-json/--default-cost/--topk-margin-json(evidence-driven Stage 3 cost is Calibrate plan selection's costs from measured sketch costs #679/Stage 3: evidence-driven cost (storage I/O, hand-off bytes) #615), the pre-ASAP replacement badges,--progress, and the old export format.How
stage_pipelinerefuses a workload with more than--max-combinations(default 10^4) whole-workload candidates before Stage 1 enumerates any. It exits with status 3 and writes nothing; results are never truncated. To make the check possible, the library splits at Pass 1 (run_stage1_to_stage3_from_pass1,PerComputationCandidates::combinations).--max-displayedonly caps the display.--tablevalidatestime_indexand rejects duplicate tables. Duplicate query names are rejected; the error says when the name is a default.server.py:/api/planserializes requests, validates the editor payload (types, unique table and query names), runsstage_pipeline(honoringCARGO_TARGET_DIR), and maps the combination-limit refusal to HTTP 422./api/exampleservesdag.example.json.stage-workload.jsturns a stage document into per-query pre (Stage 0) / post (selected plan) DAGs. Shared nodes merge by stage node id, and standalone scalar queries come fromscalar_roots.generate-sample.shregeneratesdag.example.jsonfrom the editor's default 3 SQL + 2 PromQL workload. Stage 3 selects among all 144 candidates.Key code interfaces added / modified
crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs,per_computation_candidates.rs,cost_model.rscrates/types/src/lib.rscrates/devtools/src/planning_stages.rscrates/devtools/src/bin/stage_pipeline.rs,bin/dag_export.rs(removed)asap-stage-pipeline/v1document:workload.queries[].languageispromqlorsqlandidis the--name; on a Pass 1 failure the document still carries the frontends' DAG and the Pass 1 error (shown):{ // Identifies the document; the viewer refuses any other format. "format": "asap-stage-pipeline/v1", // The queries planned, in workload order, with their requirements. "workload": { "queries": [{ "id": "q1", // --name, default q<n> "language": "sql", // "sql" or "promql" "text": "SELECT service, COUNT(*) FROM metrics GROUP BY service", // the query as given "requirements": { "accuracy": { "epsilon": 0.01, "delta": 0.01 } } // "exact" without --epsilon }] }, // Stage 0: the query-language frontends' DAG over all queries. "stage0_query_language_frontends": { "dag": { // Flat nodes; each has operator, schema, result_kind, timing, materialization and guarantee. "nodes": [], // One root per query, by node index, e.g. [{"Operator": 2}, {"Operator": 6}]. "roots": [] } }, // Stage 1: how many logical candidates were enumerated, whether the document // shows fewer, the shown ones, and a Pass 1 error when there are none. "stage1_logical_asap_optimization": { "combinations": 0, "capped": false, "candidates": [], "error": "Pass 1: ..." }, // Stage 2: physical candidates and the logical ones pruned, with reasons. "stage2_physical_asap_optimization": { "candidates": [], "pruned": [] }, // Stage 3: each shown candidate's cost, the selected id (null if none) and rejections. "stage3_plan_selection": { "costs": {}, "selected": null, "rejected": [] } }tools/dag-viewer/server.py,render.pytools/dag-viewer/stage-workload.js(new),viewer.jsUsage, end to end (run at
9ead2a5a): the editor's "Plan selected workload" and the CLI take the same path. What the user writes is one SQL and one PromQL query, the SQL table, and the accuracy;server.py's/api/planturns the editor's request into exactly thesestage_pipelinearguments (plan_args).Inside
stage_pipeline(bin/stage_pipeline.rs), each value is used once:What the user gets in
run.json(and in the editor):The viewer then reads the document with no other input:
Before this PR
After this PR
Verification
3cb19154:testandformat-and-lintpassed.cargo fmt --checkandcargo clippy -D warningsclean.cargo nextest run --workspace: 1556 passed, 50 skipped. Includes the 3^9 combination limit, duplicate tables and names,time_indexchecks, the Pass 1 failure document, and thatexamples/scalar-queries.jsonis current.test_render.py: 27 OK, stage-document only, including scalar-query placement and the candidate table and header.test_server.py: 11 OK, including mixed-language order, request validation, one request at a time, and the 422 refusal.verify_metricsql_external_consumer.shOK. The Example 1 fixture regenerates byte-identical.Limitations and follow-ups
dag_export/asap_types::cost(viewer README, run-a-query, several develop and design docs) are left for Update the docs after the G1–G11 consolidation groups land #677.q5), not by editor name.Human review — do not complete with an agent