sparkforensics-server 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-5hDGIXi4.js} +1 -1
- package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CT2SCnJJ.js} +1 -1
- package/public/assets/{CachingOpportunity-Cfw3wAFa.js → CachingOpportunity-D_O_WkbH.js} +1 -1
- package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-B38OiAOp.js} +1 -1
- package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-BoplryCH.js} +1 -1
- package/public/assets/{CoreUsageArea-BObEeenE.js → CoreUsageArea-csgwNxkl.js} +1 -1
- package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-BgKodJl2.js} +2 -2
- package/public/assets/{DisclosureContext-Ds0hcZFn.js → DisclosureContext-B0siMVdg.js} +1 -1
- package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-BqS2A2j4.js} +1 -1
- package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-Dp52QGWo.js} +1 -1
- package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-CT1yzR_s.js} +1 -1
- package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-C7Qkjtjn.js} +1 -1
- package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-WHl3ELfh.js} +1 -1
- package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-D8IUDHJI.js} +1 -1
- package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-6oSSAQ_R.js} +1 -1
- package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
- package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-BwdFL-mw.js} +1 -1
- package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DcxmiQWL.js} +1 -1
- package/public/assets/{PlanGraphRoute-NV5mCXRm.js → PlanGraphRoute-pqnHTt4i.js} +1 -1
- package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-BTbeHkJc.js} +1 -1
- package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-CRhrYNE0.js} +1 -1
- package/public/assets/{Skew-PL79TppX.js → Skew-DSNXRG9V.js} +1 -1
- package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-B7Lwu2EU.js} +1 -1
- package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-C082c2rd.js} +1 -1
- package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-BS2vpadr.js} +1 -1
- package/public/assets/{Spill-CH1bNpxL.js → Spill-B4DVKHXx.js} +1 -1
- package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-D3ac9Sou.js} +1 -1
- package/public/assets/{StageFindingGroup-B1A0nf-e.js → StageFindingGroup-D9naK109.js} +1 -1
- package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-pI1nTwVf.js} +1 -1
- package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-eO5le8iG.js} +1 -1
- package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BaNyfNvL.js} +1 -1
- package/public/assets/{Straggler-DHLTiBY-.js → Straggler-D5Fnok_y.js} +1 -1
- package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-BWnC-K_G.js} +1 -1
- package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-8_5lpq_2.js} +1 -1
- package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DJ0XSwth.js} +1 -1
- package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cys8Z6Yn.js} +1 -1
- package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-BTQndbof.js} +1 -1
- package/public/assets/index-DCeWge12.js +13 -0
- package/public/assets/parser-worker-DmeCnLWU.js +122 -0
- package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
- package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
- package/public/assets/{select-DkjFHlXR.js → select-DIYN6D3y.js} +1 -1
- package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-BxvdFnPk.js} +1 -1
- package/public/assets/{useRecentFiles-Bb4-ukVz.js → useRecentFiles-C3Ov47U7.js} +4 -4
- package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-B8SRfAoG.js} +1 -1
- package/public/docs/404.html +2 -2
- package/public/docs/assets/{app.yjczoibf.js → app.D2GqAkhy.js} +1 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
- package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
- package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.BuxVpb0H.js} +2 -2
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_board-widgets.md.D0Kkk_r5.lean.js → contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.w4lwybdh.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
- package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +22 -0
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
- package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
- package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
- package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.js +55 -0
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js +26 -0
- package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
- package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
- package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
- package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
- package/public/docs/assets/{user-guide_run-comparison.md.qin0a4IX.js → user-guide_run-comparison.md.B6aNtDYF.js} +1 -1
- package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +1 -0
- package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
- package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
- package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
- package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
- package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
- package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
- package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
- package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
- package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
- package/public/docs/contributor-guide/architecture/index.html +5 -5
- package/public/docs/contributor-guide/architecture/overview.html +5 -5
- package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
- package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
- package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
- package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
- package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
- package/public/docs/contributor-guide/contributing.html +4 -4
- package/public/docs/contributor-guide/development-setup.html +4 -4
- package/public/docs/contributor-guide/testing.html +4 -4
- package/public/docs/hashmap.json +1 -1
- package/public/docs/index.html +3 -3
- package/public/docs/tuning-reference/anti-patterns.html +3 -3
- package/public/docs/tuning-reference/aqe.html +3 -3
- package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
- package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
- package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
- package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
- package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
- package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
- package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
- package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
- package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
- package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
- package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
- package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
- package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
- package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
- package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
- package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
- package/public/docs/tuning-reference/caching.html +3 -3
- package/public/docs/tuning-reference/cluster-config.html +3 -3
- package/public/docs/tuning-reference/config.html +3 -3
- package/public/docs/tuning-reference/data-formats.html +3 -3
- package/public/docs/tuning-reference/index.html +3 -3
- package/public/docs/tuning-reference/intro.html +3 -3
- package/public/docs/tuning-reference/joins.html +3 -3
- package/public/docs/tuning-reference/memory-model.html +3 -3
- package/public/docs/tuning-reference/metrics.html +3 -3
- package/public/docs/tuning-reference/partitioning.html +3 -3
- package/public/docs/tuning-reference/pyspark.html +3 -3
- package/public/docs/tuning-reference/shuffle.html +3 -3
- package/public/docs/tuning-reference/spark-architecture.html +3 -3
- package/public/docs/tuning-reference/table-formats.html +3 -3
- package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
- package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
- package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
- package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -0
- package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
- package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
- package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
- package/public/docs/user-guide/getting-started.html +5 -28
- package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -0
- package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
- package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
- package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
- package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
- package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
- package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
- package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
- package/public/docs/user-guide/mcp-tools.html +8 -140
- package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
- package/public/docs/user-guide/run-comparison.html +5 -5
- package/public/docs/user-guide/understanding-findings.html +5 -5
- package/public/export-template.html +44 -44
- package/public/index.html +4 -4
- package/vendor-core/analyzer.js +1 -1
- package/vendor-core/api-merge-writes.js +80 -0
- package/vendor-core/comparison-output.js +49 -0
- package/vendor-core/core-source-hash.txt +1 -1
- package/vendor-core/delta-commands.js +13 -0
- package/vendor-core/delta-log-paths.js +42 -0
- package/vendor-core/delta-targets.js +127 -0
- package/vendor-core/event-handlers.js +95 -9
- package/vendor-core/event-schemas.js +3 -1
- package/vendor-core/evidence-report.js +13 -4
- package/vendor-core/mcp-server-factory.js +13 -6
- package/vendor-core/mcp-tools.js +44 -35
- package/vendor-core/plan-graph-model.js +1 -1
- package/vendor-core/plan-summary.js +13 -10
- package/vendor-core/run-comparison.js +79 -120
- package/vendor-core/run-output.js +26 -0
- package/vendor-core/stage-alignment.js +262 -0
- package/vendor-core/stage-identity.js +112 -0
- package/vendor-core/types.js +5 -0
- package/vendor-core/write-target-args.js +48 -0
- package/vendor-core/write-targets.js +35 -52
- package/public/assets/index-DaC8A2FS.js +0 -13
- package/public/assets/parser-worker-CfDvPkwF.js +0 -122
- package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
- package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
- package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
- /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
- /package/public/docs/assets/{user-guide_understanding-findings.md.DzReyCXb.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
package/vendor-core/mcp-tools.js
CHANGED
|
@@ -6,13 +6,14 @@ import { collectRun } from './cli/collect-run.js';
|
|
|
6
6
|
import { deriveEvidenceAvailability } from './evidence-availability.js';
|
|
7
7
|
import { resolveFromShs, DEFAULT_MAX_ARCHIVE_BYTES, DEFAULT_IDLE_TIMEOUT_MS } from './shs-load.js';
|
|
8
8
|
import { mcpError } from './mcp-error.js';
|
|
9
|
-
import { buildEvidenceReport, toFindingsFilter, } from './evidence-report.js';
|
|
10
|
-
import { redactComparison } from './redact.js';
|
|
9
|
+
import { buildEvidenceReport, runFindings, toFindingsFilter, } from './evidence-report.js';
|
|
11
10
|
import { computeWallClock } from './wall-clock.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { renderComparisonMarkdown, } from './run-comparison.js';
|
|
12
|
+
import { buildComparisonOutput, } from './comparison-output.js';
|
|
13
|
+
import { compileNormalizePatterns } from './stage-alignment.js';
|
|
14
|
+
import { runOutputBlocks } from './run-output.js';
|
|
15
15
|
import { evaluateBudgets, } from './cli/budgets.js';
|
|
16
|
+
import { combineRegressionBudgets } from './cli/regression-budgets.js';
|
|
16
17
|
import { FINDING_NAMES, titleCase } from './finding-names.js';
|
|
17
18
|
import { docAnchorForType } from './detector-docs.js';
|
|
18
19
|
import { DETECTORS, } from './detectors.js';
|
|
@@ -46,19 +47,15 @@ import { typeTag } from './format-utils.js';
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
// compareRuns returns
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
50
|
+
// compareRuns returns the shared comparison projection (comparison-output.ts, the CLI's `comparison`
|
|
51
|
+
// object), not the full raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
|
|
52
|
+
// `verdict` is the dashboard comparison page's headline (baseline = runIdA, candidate = runIdB).
|
|
53
|
+
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
|
|
62
59
|
|
|
63
60
|
|
|
64
61
|
function envInt(name , fallback ) {
|
|
@@ -192,6 +189,7 @@ export function diagnoseRun(runId , opts
|
|
|
192
189
|
|
|
193
190
|
|
|
194
191
|
|
|
192
|
+
|
|
195
193
|
{
|
|
196
194
|
const appModel = getCachedAppModel(runId);
|
|
197
195
|
const findingsFilter = toFindingsFilter(opts?.impactBand, opts?.type, opts?.stageId);
|
|
@@ -203,6 +201,9 @@ export function diagnoseRun(runId , opts
|
|
|
203
201
|
return {
|
|
204
202
|
runId, verdict: json.verdict, findings: json.findings, recommendations: json.recommendations, cleanChecks: json.cleanChecks,
|
|
205
203
|
notRunChecks: json.notRunChecks,
|
|
204
|
+
// The CLI report's own blocks, so a run reads the same through either surface.
|
|
205
|
+
writeTargets: json.writeTargets,
|
|
206
|
+
...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds }),
|
|
206
207
|
runComplete: appModel.app?.endTime != null,
|
|
207
208
|
// Top level too, so a client that never asks for `summary` still sees the run was tuned.
|
|
208
209
|
...(tuned ? { tunedThresholds: tuned } : {}),
|
|
@@ -345,16 +346,19 @@ async function resolveAndAnalyze(
|
|
|
345
346
|
ref , thresholds ,
|
|
346
347
|
) {
|
|
347
348
|
const { runId, appModel } = await resolveOrCreateRun(ref);
|
|
348
|
-
|
|
349
|
-
appModel.app, appModel.stages, appModel.executors.added, appModel.executors.removed,
|
|
350
|
-
appModel.jobs, appModel.sql, appModel.runAggregates, { thresholds },
|
|
351
|
-
);
|
|
352
|
-
return { runId, appModel, catalog };
|
|
349
|
+
return { runId, appModel, catalog: runFindings(appModel, thresholds).catalog };
|
|
353
350
|
}
|
|
354
351
|
|
|
355
352
|
export async function compareRuns(
|
|
356
|
-
a , b , opts
|
|
353
|
+
a , b , opts
|
|
354
|
+
,
|
|
357
355
|
) {
|
|
356
|
+
// Before any log is read: a bad pattern refuses the call, as the CLI's usage error does.
|
|
357
|
+
try {
|
|
358
|
+
compileNormalizePatterns(opts?.normalizePath);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
throw mcpError('access-or-upstream-failure', `normalizePath: ${(e ).message}`);
|
|
361
|
+
}
|
|
358
362
|
const [
|
|
359
363
|
{ runId: runIdA, appModel: appModelA, catalog: catalogA },
|
|
360
364
|
{ runId: runIdB, appModel: appModelB, catalog: catalogB },
|
|
@@ -364,26 +368,20 @@ export async function compareRuns(
|
|
|
364
368
|
// interactive stage-detail drill-down, which none of compare/matchStages/metricDeltas/findingsDelta
|
|
365
369
|
// read. Findings/metrics come from `catalog` and appModel.stages/sql, both fully populated. Produces
|
|
366
370
|
// identical output to the dashboard; the MCP tool just never exposes per-task drill-down.
|
|
367
|
-
const
|
|
371
|
+
const { comparison, output } = buildComparisonOutput(
|
|
368
372
|
{ label: runIdA, appModel: appModelA, catalog: catalogA },
|
|
369
373
|
{ label: runIdB, appModel: appModelB, catalog: catalogB },
|
|
374
|
+
{ redact: opts?.redact, normalizePath: opts?.normalizePath, view: opts?.include?.includes('stagePairs') ? 'full' : 'summary' },
|
|
370
375
|
);
|
|
371
|
-
// Stage names throughout `built` carry raw Spark stage text, which can embed a host/IP token as
|
|
372
|
-
// free text, the same residual redactReport() already scrubs from the evidence report.
|
|
373
|
-
const result = opts?.redact ? redactComparison(built) : built;
|
|
374
|
-
const verdict = comparisonVerdict(result);
|
|
375
376
|
|
|
376
377
|
return {
|
|
377
378
|
runIdA,
|
|
378
379
|
runIdB,
|
|
379
|
-
|
|
380
|
-
findingsDelta:
|
|
381
|
-
metricDeltas:
|
|
382
|
-
confidence: result.confidence,
|
|
383
|
-
reason: result.reason,
|
|
384
|
-
matchedCoverage: result.matchedCoverage,
|
|
380
|
+
...output,
|
|
381
|
+
findingsDelta: output.findings,
|
|
382
|
+
metricDeltas: output.metrics,
|
|
385
383
|
...tunedField(opts?.thresholds),
|
|
386
|
-
...(opts?.markdown ? { markdown: renderComparisonMarkdown(
|
|
384
|
+
...(opts?.markdown ? { markdown: renderComparisonMarkdown(comparison, output.verdict, tunedDetectors(opts?.thresholds)) } : {}),
|
|
387
385
|
};
|
|
388
386
|
}
|
|
389
387
|
|
|
@@ -408,6 +406,16 @@ export async function evaluateBudgetsForRun(
|
|
|
408
406
|
if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
|
|
409
407
|
throw mcpError('access-or-upstream-failure', 'regressionMetric requires maxRegressionPct.');
|
|
410
408
|
}
|
|
409
|
+
// The CLI's rule: a metric is budgeted once across the single pair and the list.
|
|
410
|
+
try {
|
|
411
|
+
const pair = budgets.maxRegressionPct === undefined ? [] : [{ metric: budgets.regressionMetric ?? 'wallClock', maxPct: budgets.maxRegressionPct }];
|
|
412
|
+
combineRegressionBudgets([
|
|
413
|
+
...pair.map((budget) => ({ origin: 'maxRegressionPct/regressionMetric', budget })),
|
|
414
|
+
...(budgets.regressionBudgets ?? []).map((budget) => ({ origin: 'regressionBudgets', budget })),
|
|
415
|
+
]);
|
|
416
|
+
} catch (e) {
|
|
417
|
+
throw mcpError('access-or-upstream-failure', (e ).message);
|
|
418
|
+
}
|
|
411
419
|
const [first, second] = await Promise.all([
|
|
412
420
|
resolveAndAnalyze(primary, opts?.thresholds),
|
|
413
421
|
secondary ? resolveAndAnalyze(secondary, opts?.thresholds) : Promise.resolve(undefined),
|
|
@@ -418,11 +426,12 @@ export async function evaluateBudgetsForRun(
|
|
|
418
426
|
// apply to the candidate.
|
|
419
427
|
const candidate = second ?? first;
|
|
420
428
|
const baseline = second ? first : undefined;
|
|
429
|
+
// Not redacted: no budget reads the stage text redaction rewrites (the parity suite checks it).
|
|
421
430
|
const comparison = baseline
|
|
422
|
-
?
|
|
431
|
+
? buildComparisonOutput(
|
|
423
432
|
{ label: baseline.runId, appModel: baseline.appModel, catalog: baseline.catalog },
|
|
424
433
|
{ label: candidate.runId, appModel: candidate.appModel, catalog: candidate.catalog },
|
|
425
|
-
)
|
|
434
|
+
).comparison
|
|
426
435
|
: undefined;
|
|
427
436
|
|
|
428
437
|
const { results, violated, inconclusive } = evaluateBudgets({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Flattens a resolved planTree into a { nodes, edges } graph shape for the
|
|
2
2
|
// React Flow + Dagre plan graph view ("Plan graph view" in
|
|
3
|
-
// docs-site/contributor-guide/architecture/drill-down.md). The Exchange
|
|
3
|
+
// docs-site/contributor-guide/architecture/drill-down/plan-graph.md). The Exchange
|
|
4
4
|
// write/read split is done upstream now, in resolvePlanTree
|
|
5
5
|
// (event-handlers.ts): this file just walks the already-split tree and maps
|
|
6
6
|
// each real PlanNode onto a PlanGraphNodeData. sourceNodeId still lets a
|
|
@@ -44,6 +44,17 @@ function pushLongFilterWarning(result , len ) {
|
|
|
44
44
|
result.warnings.push({ type: 'longFilterCondition', detail: `Filter condition is ${len} characters: consider simplifying or pushing the filter down closer to the scan.` });
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// True when a node reads internal Delta metadata: the _delta_log state scan or an anonymous
|
|
48
|
+
// commit/checkpoint read. `Location:` is often truncated to `Del...`, so it also keys off
|
|
49
|
+
// DeltaLogFileIndex or the Delta-log action column signature. (Column-signature heuristic: a
|
|
50
|
+
// future Spark renaming those action columns would leak a noise row; acceptable.) Shared by
|
|
51
|
+
// `scanRelationId` and the comparison's Delta bookkeeping flag (stage-alignment.ts).
|
|
52
|
+
export function isDeltaLogRead(name , detail ) {
|
|
53
|
+
return /_delta_log/.test(name) || /_delta_log/.test(detail) ||
|
|
54
|
+
/DeltaLogFileIndex/i.test(name + detail) ||
|
|
55
|
+
/checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail);
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
// Stable relation-identity key for a scan node: "<format>:<relation>" (e.g.
|
|
48
59
|
// "delta:mx.store_map", "parquet:warehouse.sales", "jdbc:dw.dim_product")
|
|
49
60
|
// or null when the node is internal Delta metadata / a non-scan / un-nameable.
|
|
@@ -70,16 +81,8 @@ export function scanRelationId(name , detail ) {
|
|
|
70
81
|
return `${format}:${table.replace(/^spark_catalog\./, '')}`;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
|
-
// 2. Internal Delta metadata (
|
|
74
|
-
|
|
75
|
-
// so also key off DeltaLogFileIndex or the Delta-log action column signature.
|
|
76
|
-
// (Column-signature heuristic: a future Spark renaming those action columns
|
|
77
|
-
// would leak a noise row; acceptable.)
|
|
78
|
-
if (/_delta_log/.test(name) || /_delta_log/.test(detail) ||
|
|
79
|
-
/DeltaLogFileIndex/i.test(name + detail) ||
|
|
80
|
-
/checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail)) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
84
|
+
// 2. Internal Delta metadata (see isDeltaLogRead) → null.
|
|
85
|
+
if (isDeltaLogRead(name, detail)) return null;
|
|
83
86
|
|
|
84
87
|
// 3. Anonymous real file read (InMemoryFileIndex, no catalog name) →
|
|
85
88
|
// best-effort Location basename. Preserves temp/snapshot self-reuse; a
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computeWallClock } from './wall-clock.js';
|
|
2
2
|
import { normalizeDetail } from './detectors.js';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeStageName, stageIdentityWith, identityIndexWith, pairEqualCounts } from './stage-identity.js';
|
|
4
4
|
import { computeAllocation } from './allocation.js';
|
|
5
5
|
import { totalExecutorCpuMs, withEarlierAttempts } from './run-totals.js';
|
|
6
|
-
import { planNodesOfStage } from './stage-plan-nodes.js';
|
|
7
6
|
import { captureSnapshot } from './session-snapshot.js';
|
|
7
|
+
import { alignStages, compileNormalizePatterns, COMPARISON_SCHEMA_VERSION, } from './stage-alignment.js';
|
|
8
8
|
import { tunedRunNote } from './threshold-overrides.js';
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
import { isIncompleteRun } from './check-coverage.js';
|
|
@@ -21,13 +21,26 @@ import { summarizeRunOutcome } from './run-outcome.js';
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
|
|
31
44
|
|
|
32
45
|
|
|
33
46
|
|
|
@@ -41,68 +54,10 @@ function jobOutcome(snapshot ) {
|
|
|
41
54
|
return { failedJobs, totalJobs, incomplete: isIncompleteRun(snapshot.catalog) };
|
|
42
55
|
}
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
// the same logical stage across two runs normalizes to one identity.
|
|
46
|
-
export function normalizeStageName(name ) {
|
|
47
|
-
return String(name)
|
|
48
|
-
.toLowerCase()
|
|
49
|
-
.replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
|
|
50
|
-
.replace(/\d+/g, '#')
|
|
51
|
-
.replace(/\s+/g, ' ')
|
|
52
|
-
.trim();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Bottom-up, order-independent structural identity of a resolved plan tree:
|
|
56
|
-
// each node folds its normalized name/detail with its children's digests
|
|
57
|
-
// (children sorted, so AQE picking a different broadcast side still matches),
|
|
58
|
-
// so two plans collide only when their whole shape and every node's detail
|
|
59
|
-
// agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
|
|
60
|
-
// codegen numbers, AQE build-side choice, commutative-operand order). Each
|
|
61
|
-
// node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
|
|
62
|
-
// detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
|
|
63
|
-
// instead of embedding full child identity strings, which would re-escape
|
|
64
|
-
// every level below and blow identity size up to ~2^depth on the 20-60+
|
|
65
|
-
// operator-deep plans real Spark produces.
|
|
66
|
-
function planTreeIdentity(root ) {
|
|
67
|
-
if (!root) return null;
|
|
68
|
-
function visit(node ) {
|
|
69
|
-
const childDigests = (node.children ?? []).map(visit).sort();
|
|
70
|
-
return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
|
|
71
|
-
}
|
|
72
|
-
return visit(root);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Plan identity for the stage's SQL execution, scoped to only the plan nodes
|
|
76
|
-
// this stage actually ran (`node.stageIds`), not the whole tree: two stages
|
|
77
|
-
// sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
|
|
78
|
-
// collapse onto one identity regardless of which part of the plan each ran.
|
|
79
|
-
// Falls back to the coarser whole-tree identity when the stage has no
|
|
80
|
-
// attributed nodes (hand-built snapshots without `stageIds`, or unmatched
|
|
81
|
-
// accumulables).
|
|
82
|
-
function sqlNodeIdentity(stage , snapshot ) {
|
|
83
|
-
const execId = stage.sqlExecutionId;
|
|
84
|
-
if (execId == null) return '';
|
|
85
|
-
const root = snapshot.sql.get(execId)?.planTree ?? null;
|
|
86
|
-
if (!root) return '';
|
|
87
|
-
const fingerprints = planNodesOfStage(stage, snapshot.sql)
|
|
88
|
-
.map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
|
|
89
|
-
if (fingerprints.length === 0) return planTreeIdentity(root) ?? '';
|
|
90
|
-
return cyrb53(JSON.stringify(fingerprints.sort()));
|
|
91
|
-
}
|
|
57
|
+
export { normalizeStageName };
|
|
92
58
|
|
|
93
59
|
export function stageIdentity(stage , snapshot ) {
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function identityIndex(snapshot ) {
|
|
98
|
-
const byIdentity = new Map ();
|
|
99
|
-
for (const [id, stage] of snapshot.stages) {
|
|
100
|
-
const key = stageIdentity(stage, snapshot);
|
|
101
|
-
const ids = byIdentity.get(key);
|
|
102
|
-
if (ids) ids.push(id);
|
|
103
|
-
else byIdentity.set(key, [id]);
|
|
104
|
-
}
|
|
105
|
-
return byIdentity;
|
|
60
|
+
return stageIdentityWith(stage, snapshot, normalizeDetail);
|
|
106
61
|
}
|
|
107
62
|
|
|
108
63
|
export function matchStages(baseSnap , candSnap )
|
|
@@ -111,10 +66,8 @@ export function matchStages(baseSnap , candSnap
|
|
|
111
66
|
|
|
112
67
|
|
|
113
68
|
{
|
|
114
|
-
const baseIdx =
|
|
115
|
-
const candIdx =
|
|
116
|
-
const pairs = [];
|
|
117
|
-
const matchedIdentities = new Set ();
|
|
69
|
+
const baseIdx = identityIndexWith(baseSnap, (stage) => stageIdentity(stage, baseSnap));
|
|
70
|
+
const candIdx = identityIndexWith(candSnap, (stage) => stageIdentity(stage, candSnap));
|
|
118
71
|
const collisionIdentities = new Set ();
|
|
119
72
|
// A collision is a single-run property: more than one stage in the SAME run
|
|
120
73
|
// shares an identity. Recorded per-run regardless of whether the other run
|
|
@@ -122,26 +75,7 @@ export function matchStages(baseSnap , candSnap
|
|
|
122
75
|
// don't, so this set means "was ambiguous", not "stayed unpaired".
|
|
123
76
|
for (const idx of [baseIdx, candIdx])
|
|
124
77
|
for (const [identity, ids] of idx) if (ids.length > 1) collisionIdentities.add(identity);
|
|
125
|
-
|
|
126
|
-
// *count*, so pair its stages off positionally by sorted id rather than
|
|
127
|
-
// dropping them. This is exact when the identity actually distinguishes
|
|
128
|
-
// stages (e.g. self-comparing a run: every stage matches itself). It's a
|
|
129
|
-
// best-effort guess when the identity is coarse (no SQL/attribution) and
|
|
130
|
-
// the two sides are genuinely different runs -- two unrelated same-named
|
|
131
|
-
// stages could get cross-paired. Accepted tradeoff: dropping them instead
|
|
132
|
-
// would also sacrifice the exact-self-comparison case, which matters more.
|
|
133
|
-
for (const identity of [...baseIdx.keys()].sort()) {
|
|
134
|
-
const candIds = candIdx.get(identity);
|
|
135
|
-
if (!candIds) continue;
|
|
136
|
-
const baseIds = baseIdx.get(identity) ;
|
|
137
|
-
if (baseIds.length !== candIds.length) continue;
|
|
138
|
-
const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
|
|
139
|
-
const sortedCandIds = [...candIds].sort((a, b) => a - b);
|
|
140
|
-
for (let i = 0; i < sortedBaseIds.length; i++) {
|
|
141
|
-
pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
|
|
142
|
-
}
|
|
143
|
-
matchedIdentities.add(identity);
|
|
144
|
-
}
|
|
78
|
+
const { pairs, matchedIdentities } = pairEqualCounts(baseIdx, candIdx);
|
|
145
79
|
const total = baseSnap.stages.size + candSnap.stages.size;
|
|
146
80
|
// Both runs stage-less: nothing to compare, not "nothing matched".
|
|
147
81
|
const coverage = total === 0 ? 1 : (2 * pairs.length) / total;
|
|
@@ -348,19 +282,19 @@ function namesConflict(a , b )
|
|
|
348
282
|
}
|
|
349
283
|
|
|
350
284
|
// A pair's skew ratio is null when the stage's duration wasn't measurable (see
|
|
351
|
-
// `stageSkewRatio`).
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}).sort((x, y) => x.
|
|
285
|
+
// `stageSkewRatio`). Rows come from the aligner's `stagePairs`, so the table and the coverage
|
|
286
|
+
// banner agree. A pair covers one stage per side here; `pairId` is the unique row key.
|
|
287
|
+
function stageSkewDeltas(baseSnap , candSnap , stagePairs ) {
|
|
288
|
+
return stagePairs.map((p) => {
|
|
289
|
+
const baseId = p.baseStageIds[0], candId = p.candStageIds[0];
|
|
290
|
+
const baseStage = baseSnap.stages.get(baseId) ;
|
|
291
|
+
const b = stageSkewRatio(baseStage);
|
|
292
|
+
const c = stageSkewRatio(candSnap.stages.get(candId) );
|
|
293
|
+
return {
|
|
294
|
+
pairId: p.pairId, name: normalizeStageName(baseStage.name ?? ''), baseId, candId,
|
|
295
|
+
baseline: b, candidate: c, delta: b != null && c != null ? c - b : null,
|
|
296
|
+
};
|
|
297
|
+
}).sort((x, y) => x.name < y.name ? -1 : x.name > y.name ? 1 : x.baseId - y.baseId);
|
|
364
298
|
}
|
|
365
299
|
|
|
366
300
|
// Compact, view-friendly per-stage record for the manual stage-pinning panel.
|
|
@@ -402,6 +336,14 @@ function stageList(snap )
|
|
|
402
336
|
// caller's reference, so one shared empty Map is safe here.
|
|
403
337
|
const EMPTY_TASK_DATA = new Map ();
|
|
404
338
|
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
405
347
|
// Wraps the analyze()-to-compareRuns() snapshot-building sequence shared by
|
|
406
348
|
// the CLI's --baseline path and mcp-tools.ts's compareRuns tool: both need
|
|
407
349
|
// captureSnapshot (with an empty taskDataCache, the interactive drill-down
|
|
@@ -410,50 +352,65 @@ const EMPTY_TASK_DATA = new Map ();
|
|
|
410
352
|
export function buildComparison(
|
|
411
353
|
baseline ,
|
|
412
354
|
candidate ,
|
|
355
|
+
options ,
|
|
413
356
|
) {
|
|
414
357
|
return compareRuns(
|
|
415
358
|
{ label: baseline.label, snapshot: captureSnapshot(baseline.appModel, baseline.catalog, EMPTY_TASK_DATA) },
|
|
416
359
|
{ label: candidate.label, snapshot: captureSnapshot(candidate.appModel, candidate.catalog, EMPTY_TASK_DATA) },
|
|
360
|
+
options,
|
|
417
361
|
);
|
|
418
362
|
}
|
|
419
363
|
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
// comparison is dominated by guesswork, not genuine pairing. 0.5 is thus the
|
|
425
|
-
// natural midpoint for "more matched than not," not an arbitrary tuning knob.
|
|
426
|
-
const LOW_COVERAGE_THRESHOLD = 0.5;
|
|
364
|
+
// `ok` needs this share of both runs' executor run time to sit in paired stages. Run time, not
|
|
365
|
+
// stage count: the stages a run spends its time in decide whether a per-stage delta compares the
|
|
366
|
+
// same work, and a count lets many small matched stages hide an unmatched heavy one.
|
|
367
|
+
const RUNTIME_COVERAGE_THRESHOLD = 0.9;
|
|
427
368
|
|
|
428
369
|
export function compareRuns(
|
|
429
370
|
baseline ,
|
|
430
371
|
candidate ,
|
|
372
|
+
options = {},
|
|
431
373
|
) {
|
|
432
374
|
const baseSnap = baseline.snapshot, candSnap = candidate.snapshot;
|
|
375
|
+
// The old exact matcher stays the source of `matchedCoverage`; pairs and the gate come from the aligner.
|
|
433
376
|
const match = matchStages(baseSnap, candSnap);
|
|
377
|
+
const alignment = alignStages(baseSnap, candSnap, { normalizePath: compileNormalizePatterns(options.normalizePath) });
|
|
378
|
+
const runtimeCoverage = alignment.runtimeCoverage;
|
|
434
379
|
// Name equality is a weak confidence signal, not a hard gate: renaming a job
|
|
435
380
|
// is the normal way to label an A/B experiment, so a mismatch must not block
|
|
436
381
|
// the (matching-free, name-independent) deltas. Surface it as `low` instead.
|
|
437
382
|
const namesDiffer = namesConflict(baseSnap.app, candSnap.app);
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
383
|
+
// Runtime coverage is the other half of the signal: identical names on two runs whose heavy
|
|
384
|
+
// stages do not pair are just as misleading as differing names on two runs that pair well, so
|
|
385
|
+
// either condition alone drops confidence to `low`. No run time at all is `insufficient`.
|
|
386
|
+
const insufficient = runtimeCoverage === null;
|
|
387
|
+
const lowCoverage = runtimeCoverage !== null && runtimeCoverage < RUNTIME_COVERAGE_THRESHOLD;
|
|
388
|
+
const confidence = insufficient ? 'insufficient' : namesDiffer || lowCoverage ? 'low' : 'ok';
|
|
389
|
+
// Rounded down, so a share just under the gate never reads as 90%.
|
|
390
|
+
const share = runtimeCoverage === null ? '' : `${Math.floor(runtimeCoverage * 100)}%`;
|
|
391
|
+
const reason = insufficient
|
|
392
|
+
? `${namesDiffer ? 'Run names differ and no' : 'No'} executor run time was recorded in either run, so there is no work to compare.`
|
|
393
|
+
: namesDiffer && lowCoverage
|
|
394
|
+
? `Run names differ and only ${share} of executor run time is in matched stages, so deltas may compare different work.`
|
|
444
395
|
: namesDiffer
|
|
445
396
|
? 'Run names differ, so deltas may compare different work.'
|
|
446
397
|
: lowCoverage
|
|
447
|
-
? `Only ${
|
|
398
|
+
? `Only ${share} of executor run time is in matched stages, so per-stage rows mostly compare unrelated work.`
|
|
448
399
|
: null;
|
|
449
400
|
return {
|
|
450
401
|
baselineLabel: baseline.label, candidateLabel: candidate.label,
|
|
451
|
-
confidence
|
|
402
|
+
confidence,
|
|
452
403
|
reason,
|
|
453
404
|
matchedCoverage: match.coverage,
|
|
405
|
+
comparisonSchemaVersion: COMPARISON_SCHEMA_VERSION,
|
|
406
|
+
runtimeCoverage,
|
|
407
|
+
stagePairs: alignment.pairs,
|
|
408
|
+
unmatched: alignment.unmatched,
|
|
409
|
+
replanned: alignment.replanned,
|
|
410
|
+
bookkeepingStageIds: alignment.bookkeepingStageIds,
|
|
454
411
|
metrics: metricDeltas(baseSnap, candSnap),
|
|
455
412
|
findings: findingsDelta(baseSnap, candSnap),
|
|
456
|
-
stageSkew: stageSkewDeltas(baseSnap, candSnap,
|
|
413
|
+
stageSkew: stageSkewDeltas(baseSnap, candSnap, alignment.pairs),
|
|
457
414
|
baseStages: stageList(baseSnap),
|
|
458
415
|
candStages: stageList(candSnap),
|
|
459
416
|
jobOutcomes: { baseline: jobOutcome(baseSnap), candidate: jobOutcome(candSnap) },
|
|
@@ -491,11 +448,13 @@ export function renderComparisonMarkdown(
|
|
|
491
448
|
if (verdict.sentences.length > 0) lines.push('', verdict.sentences.join(' '));
|
|
492
449
|
lines.push('');
|
|
493
450
|
}
|
|
494
|
-
if (comparison.confidence
|
|
495
|
-
lines.push(`- confidence:
|
|
451
|
+
if (comparison.confidence !== 'ok') {
|
|
452
|
+
lines.push(`- confidence: ${comparison.confidence}, ${comparison.reason}`);
|
|
496
453
|
lines.push('');
|
|
497
454
|
}
|
|
498
455
|
lines.push(`- matched stage coverage: ${(comparison.matchedCoverage * 100).toFixed(1)}%`);
|
|
456
|
+
lines.push(`- runtime coverage: ${comparison.runtimeCoverage === null ? 'n/a' : `${(comparison.runtimeCoverage * 100).toFixed(1)}%`}`);
|
|
457
|
+
lines.push(`- stage pairs: ${comparison.stagePairs.length}, unmatched: ${comparison.unmatched.baseStageIds.length} baseline / ${comparison.unmatched.candStageIds.length} candidate, Delta bookkeeping: ${comparison.bookkeepingStageIds.baseStageIds.length} baseline / ${comparison.bookkeepingStageIds.candStageIds.length} candidate`);
|
|
499
458
|
lines.push('');
|
|
500
459
|
lines.push('### Metric deltas');
|
|
501
460
|
lines.push('');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { runFindings } from './evidence-report.js';
|
|
2
|
+
import { redactRunModel } from './redact.js';
|
|
3
|
+
import { computeRunMetrics, } from './run-metrics.js';
|
|
4
|
+
import { buildEffectiveConf, } from './effective-conf.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/** The CLI report's `metrics` and `effectiveConf` blocks, shared with MCP diagnose_run. Under
|
|
9
|
+
* `redact` they come from the run redacted with the report's own inputs, so host and app
|
|
10
|
+
* pseudonyms match the report's. `confKeys`/`confRedactRegex` narrow and mask `effectiveConf`. */
|
|
11
|
+
export function runOutputBlocks(
|
|
12
|
+
appModel ,
|
|
13
|
+
{ redact, thresholds, confKeys, confRedactRegex }
|
|
14
|
+
|
|
15
|
+
= {},
|
|
16
|
+
) {
|
|
17
|
+
let blocksModel = appModel;
|
|
18
|
+
if (redact) {
|
|
19
|
+
const { catalog, config } = runFindings(appModel, thresholds);
|
|
20
|
+
blocksModel = redactRunModel(appModel, catalog, config).appModel;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
metrics: computeRunMetrics(blocksModel, thresholds),
|
|
24
|
+
effectiveConf: buildEffectiveConf(blocksModel.app, { keys: confKeys, userPattern: confRedactRegex }),
|
|
25
|
+
};
|
|
26
|
+
}
|