sparkforensics-server 0.5.0 → 0.5.2

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.
Files changed (309) hide show
  1. package/package.json +3 -3
  2. package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-8t3U3isq.js} +1 -1
  3. package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CJs9tTum.js} +1 -1
  4. package/public/assets/CachingOpportunity-D1NcHs0V.js +1 -0
  5. package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-CdRJ9gD8.js} +1 -1
  6. package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-B5Gbe5js.js} +1 -1
  7. package/public/assets/CoreUsageArea-a--NTRGY.js +1 -0
  8. package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-C1hNeX49.js} +2 -2
  9. package/public/assets/DisclosureContext-ZpE6CkHx.js +1 -0
  10. package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-dobXdz7E.js} +1 -1
  11. package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-D9grwi7t.js} +1 -1
  12. package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-8-G2ri10.js} +1 -1
  13. package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-B91lky7a.js} +1 -1
  14. package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-BsWvBMyJ.js} +1 -1
  15. package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-BM5schZI.js} +1 -1
  16. package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-DdN8JIJH.js} +1 -1
  17. package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
  18. package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-CY5HDYtp.js} +1 -1
  19. package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DgHCDBD_.js} +1 -1
  20. package/public/assets/PlanGraphRoute-DH3KJb8j.js +1 -0
  21. package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-DOnE9vGF.js} +1 -1
  22. package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-MMnDESkF.js} +1 -1
  23. package/public/assets/{Skew-PL79TppX.js → Skew-BmZZJ5ms.js} +1 -1
  24. package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-CsJzfEqo.js} +1 -1
  25. package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-BoK3R1Ex.js} +1 -1
  26. package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-CnVxB5AJ.js} +1 -1
  27. package/public/assets/Spill-BhNicyBy.js +1 -0
  28. package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-mAhHU4l_.js} +1 -1
  29. package/public/assets/StageFindingGroup-BEtPWcT0.js +1 -0
  30. package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-DJkYkrsU.js} +1 -1
  31. package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-BDxRknLp.js} +1 -1
  32. package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BPRw-otN.js} +1 -1
  33. package/public/assets/{Straggler-DHLTiBY-.js → Straggler-CZCbnOWP.js} +1 -1
  34. package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-C57YcLFQ.js} +1 -1
  35. package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-Bi84qU7n.js} +1 -1
  36. package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DdQV7htE.js} +1 -1
  37. package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cl6_5hfV.js} +1 -1
  38. package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-CssZ6MT2.js} +1 -1
  39. package/public/assets/index-2h_TgUAb.css +2 -0
  40. package/public/assets/index-B-03mG7K.js +14 -0
  41. package/public/assets/parser-worker-CA5RxqKc.js +122 -0
  42. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  43. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  44. package/public/assets/{select-DkjFHlXR.js → select-CQjNhprm.js} +1 -1
  45. package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-Dyl6T8Ok.js} +1 -1
  46. package/public/assets/useRecentFiles-XZEnLlal.js +11 -0
  47. package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-Dhvit8dt.js} +1 -1
  48. package/public/docs/404.html +2 -2
  49. package/public/docs/assets/{app.yjczoibf.js → app.eYTPqTeq.js} +1 -1
  50. package/public/docs/assets/chunks/@localSearchIndexroot.u_e5bQz4.js +1 -0
  51. package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.DsZrQKQR.js} +1 -1
  52. package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.DsTNE2pE.js} +2 -2
  53. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  54. 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
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.lean.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  77. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  78. package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
  79. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  81. 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
  82. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.lean.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  107. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  108. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  109. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  110. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  111. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  112. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  113. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  114. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  115. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  116. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.js +23 -0
  117. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.lean.js +1 -0
  118. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  119. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  120. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  121. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  122. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  123. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  124. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  125. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  126. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js +56 -0
  127. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +1 -0
  128. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  129. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  130. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js +26 -0
  131. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js +1 -0
  132. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  133. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  135. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  136. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  137. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  138. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  139. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  140. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  141. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  142. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +1 -0
  143. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +1 -0
  144. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.js +1 -0
  145. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.lean.js +1 -0
  146. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.js +1 -0
  147. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.lean.js +1 -0
  148. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  149. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  150. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  151. package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
  152. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  153. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  154. package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
  155. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  156. package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
  157. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  158. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  159. package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
  160. package/public/docs/contributor-guide/architecture/index.html +5 -5
  161. package/public/docs/contributor-guide/architecture/overview.html +5 -5
  162. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  163. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  164. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  166. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  167. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  168. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  169. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  170. package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
  171. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  172. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  173. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  174. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  175. package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
  176. package/public/docs/contributor-guide/contributing.html +4 -4
  177. package/public/docs/contributor-guide/development-setup.html +4 -4
  178. package/public/docs/contributor-guide/testing.html +4 -4
  179. package/public/docs/hashmap.json +1 -1
  180. package/public/docs/index.html +3 -3
  181. package/public/docs/tuning-reference/anti-patterns.html +3 -3
  182. package/public/docs/tuning-reference/aqe.html +3 -3
  183. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
  184. package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
  185. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
  186. package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
  187. package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
  188. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
  189. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
  190. package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
  191. package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
  192. package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
  193. package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
  194. package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
  195. package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
  196. package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
  197. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
  198. package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
  199. package/public/docs/tuning-reference/caching.html +3 -3
  200. package/public/docs/tuning-reference/cluster-config.html +3 -3
  201. package/public/docs/tuning-reference/config.html +3 -3
  202. package/public/docs/tuning-reference/data-formats.html +3 -3
  203. package/public/docs/tuning-reference/index.html +3 -3
  204. package/public/docs/tuning-reference/intro.html +3 -3
  205. package/public/docs/tuning-reference/joins.html +3 -3
  206. package/public/docs/tuning-reference/memory-model.html +3 -3
  207. package/public/docs/tuning-reference/metrics.html +3 -3
  208. package/public/docs/tuning-reference/partitioning.html +3 -3
  209. package/public/docs/tuning-reference/pyspark.html +3 -3
  210. package/public/docs/tuning-reference/shuffle.html +3 -3
  211. package/public/docs/tuning-reference/spark-architecture.html +3 -3
  212. package/public/docs/tuning-reference/table-formats.html +3 -3
  213. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  214. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  215. package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
  216. package/public/docs/user-guide/getting-started/ci-and-automation.html +52 -0
  217. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  218. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  219. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  220. package/public/docs/user-guide/getting-started.html +5 -28
  221. package/public/docs/user-guide/mcp-tools/compare-runs.html +85 -0
  222. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  223. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  224. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  225. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  226. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  227. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  228. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  229. package/public/docs/user-guide/mcp-tools.html +8 -140
  230. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  231. package/public/docs/user-guide/run-comparison.html +5 -5
  232. package/public/docs/user-guide/understanding-findings.html +5 -5
  233. package/public/export-template.html +45 -45
  234. package/public/index.html +5 -5
  235. package/vendor-core/analyzer.js +1 -1
  236. package/vendor-core/api-merge-writes.js +80 -0
  237. package/vendor-core/cli/budgets.js +62 -2
  238. package/vendor-core/cli/regression-budgets.js +35 -0
  239. package/vendor-core/comparison-output.js +50 -0
  240. package/vendor-core/core-source-hash.txt +1 -1
  241. package/vendor-core/delta-commands.js +13 -0
  242. package/vendor-core/delta-log-paths.js +42 -0
  243. package/vendor-core/delta-targets.js +127 -0
  244. package/vendor-core/detectors.js +219 -56
  245. package/vendor-core/docs-content/detection/chrn.md +3 -1
  246. package/vendor-core/docs-content/detection/cold.md +2 -1
  247. package/vendor-core/docs-content/detection/plan.md +9 -2
  248. package/vendor-core/docs-content/detection/shfl.md +8 -2
  249. package/vendor-core/docs-content/detection/skew.md +11 -5
  250. package/vendor-core/docs-content/detection/slow.md +6 -2
  251. package/vendor-core/docs-content/detection/spill.md +4 -1
  252. package/vendor-core/docs-content/detection/strag.md +5 -3
  253. package/vendor-core/docs-content/detection/tiny.md +3 -2
  254. package/vendor-core/event-handlers.js +95 -9
  255. package/vendor-core/event-schemas.js +3 -1
  256. package/vendor-core/evidence-report.js +24 -15
  257. package/vendor-core/finding-presentation.js +39 -13
  258. package/vendor-core/finding-types.js +50 -6
  259. package/vendor-core/mcp-server-factory.js +17 -6
  260. package/vendor-core/mcp-tools.js +53 -35
  261. package/vendor-core/median.js +8 -0
  262. package/vendor-core/plan-graph-model.js +1 -1
  263. package/vendor-core/plan-summary.js +13 -10
  264. package/vendor-core/run-comparison.js +81 -120
  265. package/vendor-core/run-output.js +26 -0
  266. package/vendor-core/sequence-alignment.js +83 -0
  267. package/vendor-core/stage-alignment.js +586 -0
  268. package/vendor-core/stage-identity.js +112 -0
  269. package/vendor-core/stage-quantiles.js +3 -1
  270. package/vendor-core/stage-structure.js +83 -0
  271. package/vendor-core/types.js +5 -0
  272. package/vendor-core/write-target-args.js +48 -0
  273. package/vendor-core/write-targets.js +35 -52
  274. package/public/assets/CachingOpportunity-Cfw3wAFa.js +0 -1
  275. package/public/assets/CoreUsageArea-BObEeenE.js +0 -1
  276. package/public/assets/DisclosureContext-Ds0hcZFn.js +0 -1
  277. package/public/assets/PlanGraphRoute-NV5mCXRm.js +0 -1
  278. package/public/assets/Spill-CH1bNpxL.js +0 -1
  279. package/public/assets/StageFindingGroup-B1A0nf-e.js +0 -1
  280. package/public/assets/index-BcqTLKFp.css +0 -2
  281. package/public/assets/index-DaC8A2FS.js +0 -13
  282. package/public/assets/parser-worker-CfDvPkwF.js +0 -122
  283. package/public/assets/useRecentFiles-Bb4-ukVz.js +0 -11
  284. package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
  285. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
  286. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
  287. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
  288. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
  289. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
  290. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
  291. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
  292. package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
  293. package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
  294. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
  295. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
  296. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
  297. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
  298. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
  299. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
  300. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
  301. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
  302. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
  303. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
  304. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
  305. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.js +0 -1
  306. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
  307. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
  308. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.lean.js +0 -1
  309. /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
@@ -0,0 +1,586 @@
1
+ // The stage aligner: two snapshots and options in, typed stage pairs out. Pure (no I/O, no
2
+ // dependency on the CLI, MCP or UI) and deterministic; swapping baseline and candidate mirrors the
3
+ // pairs. `compareRuns` calls it, and every surface that compares runs reads its result
4
+ // (`stagePairs`) instead of comparing stage keys.
5
+ //
6
+ // Pairing runs in two levels (see `alignStages`). The exact key is `comparisonIdentity`: the
7
+ // `stageIdentity` recipe run with the comparison normalizer, so the volatile tokens that make two
8
+ // runs of one job differ (staging directories, dates, IN-lists, Delta log file counts) and the
9
+ // caller's `--normalize-path` patterns do not split a stage in two. A coarser structural key
10
+ // (`stage-structure.ts`) and a detail similarity pair what the exact key splits. `stageIdentity`
11
+ // itself stays the frozen exact key.
12
+
13
+ import { normalizeDetail } from './detectors.js';
14
+ import { isDeltaLogRead, scanRelationId } from './plan-summary.js';
15
+ import { withEarlierAttempts, totalExecutorCpuMs } from './run-totals.js';
16
+ import { planNodesOfStage } from './stage-plan-nodes.js';
17
+ import { normalizeStageName, stageIdentityWith, } from './stage-identity.js';
18
+ import { alignSequences } from './sequence-alignment.js';
19
+ import { attributeNames, detailTokens, jaccard, structuralKey } from './stage-structure.js';
20
+
21
+
22
+
23
+ /** Version of the comparison block (`stagePairs`, `unmatched`, `replanned`, `bookkeepingStageIds`,
24
+ * `runtimeCoverage`). Additive changes keep it; a consumer refuses a version it does not know. */
25
+ export const COMPARISON_SCHEMA_VERSION = 1;
26
+
27
+ // ---- Comparison normalizer ------------------------------------------------------------------
28
+
29
+ // Targeted patches for the volatile tokens that differ between two runs of one job. Each applies
30
+ // on top of `normalizeDetail`, in this order. No blanket numeric or path stripping: those fuse
31
+ // distinct stages of one run.
32
+ export const COMPARISON_PATCHES = [
33
+ // Delta log and file-index file counts track how many files or commits exist, not the query:
34
+ // DeltaLogFileIndex(4 paths) vs DeltaLogFileIndex(8 paths).
35
+ { name: 'fileCounts', apply: (s) => s.replace(/FileIndex\(\d+ paths\)/g, 'FileIndex(n paths)') },
36
+ // Date and timestamp literals: the run's processing window.
37
+ { name: 'dates', apply: (s) => s.replace(/\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)?/g, 'DATE') },
38
+ // IN-lists grow with the window. Spark prints small lists as `IN (a, b)` and large ones
39
+ // (InSet) as `INSET a, b` with no opening parenthesis.
40
+ {
41
+ name: 'inLists',
42
+ apply: (s) => s.replace(/\bIN \([^()]*\)/g, 'IN (...)').replace(/\bINSET [^()]*?(?=\))/g, 'INSET ...'),
43
+ },
44
+ // Random per-run staging directory segments (`/ds/aB3xYz/`): a 6-8 character alphanumeric path
45
+ // segment that mixes cases, or letters with digits. An ALL-CAPS or all-lowercase name survives,
46
+ // but a real directory that looks like that (`/output01/`, `/Region3/`) is rewritten too.
47
+ {
48
+ name: 'stagingDirs',
49
+ apply: (s) => s.replace(/\/([A-Za-z0-9]{6,8})(?=\/)/g, (m, seg ) =>
50
+ (/[A-Z]/.test(seg) && /[a-z]/.test(seg)) || (/[A-Za-z]/.test(seg) && /\d/.test(seg)) ? '/TMP' : m),
51
+ },
52
+ ];
53
+
54
+ /** What every match of a caller-supplied pattern is replaced with. */
55
+ export const NORMALIZE_PATH_TOKEN = 'NORMALIZED';
56
+ /** Longest caller pattern accepted. A pattern from an agent can backtrack catastrophically, so
57
+ * the length is capped; the cap does not make a bad pattern safe. */
58
+ export const MAX_NORMALIZE_PATTERN_LENGTH = 200;
59
+ export const MAX_NORMALIZE_PATTERNS = 16;
60
+
61
+ /** Compiles caller-supplied normalize patterns (global, every match replaced). Throws an Error
62
+ * naming the offending pattern when one is too long, not a valid regular expression, or matches
63
+ * the empty string (which would insert the token between every character). */
64
+ export function compileNormalizePatterns(patterns ) {
65
+ if (!patterns || patterns.length === 0) return [];
66
+ if (patterns.length > MAX_NORMALIZE_PATTERNS) {
67
+ throw new Error(`At most ${MAX_NORMALIZE_PATTERNS} normalize patterns are accepted, got ${patterns.length}.`);
68
+ }
69
+ return patterns.map((source) => {
70
+ if (typeof source !== 'string' || source.length === 0) throw new Error('A normalize pattern must be a non-empty string.');
71
+ if (source.length > MAX_NORMALIZE_PATTERN_LENGTH) {
72
+ throw new Error(`Normalize pattern "${source.slice(0, 40)}..." is ${source.length} characters; the limit is ${MAX_NORMALIZE_PATTERN_LENGTH}.`);
73
+ }
74
+ let re ;
75
+ try {
76
+ re = new RegExp(source, 'g');
77
+ } catch (e) {
78
+ throw new Error(`Normalize pattern "${source}" is not a valid regular expression: ${(e ).message}`);
79
+ }
80
+ if (re.test('')) throw new Error(`Normalize pattern "${source}" matches the empty string.`);
81
+ re.lastIndex = 0;
82
+ return re;
83
+ });
84
+ }
85
+
86
+ /** The detail normalizer the comparison keys use: caller patterns first, on the text as Spark
87
+ * printed it, then `normalizeDetail`, then the targeted patches. */
88
+ export function comparisonDetailNormalizer(callerPatterns = []) {
89
+ return (detail) => {
90
+ let s = detail;
91
+ for (const re of callerPatterns) s = s.replace(re, NORMALIZE_PATH_TOKEN);
92
+ s = normalizeDetail(s);
93
+ for (const patch of COMPARISON_PATCHES) s = patch.apply(s);
94
+ return s;
95
+ };
96
+ }
97
+
98
+ // ---- Identity and bookkeeping ---------------------------------------------------------------
99
+
100
+ /** `stageIdentity`'s recipe (normalized stage name plus the attributed nodes' fingerprint) with the
101
+ * comparison normalizer. Internal to the aligner: consumers key on a pair's `pairId`. */
102
+ export function comparisonIdentity(
103
+ stage , snapshot , normalizer , nodes ,
104
+ ) {
105
+ return stageIdentityWith(stage, snapshot, normalizer, nodes);
106
+ }
107
+
108
+ function isScanNode(node ) {
109
+ return /^(Scan|BatchScan)\b/.test(node.name ?? '') || /FileScan|JDBCRelation/.test(node.detail ?? '');
110
+ }
111
+
112
+ /** True for a stage whose scans only read Delta log files or checkpoints: it has at least one scan
113
+ * and every scan is internal Delta metadata. Such a stage is table bookkeeping, not query work.
114
+ * A stage with no attributed plan nodes has no scans to test, so it is never flagged. */
115
+ export function isDeltaBookkeepingStage(
116
+ stage , sql , nodes = planNodesOfStage(stage, sql),
117
+ ) {
118
+ let scans = 0;
119
+ for (const node of nodes) {
120
+ if (!isScanNode(node)) continue;
121
+ const name = node.name ?? '', detail = node.detail ?? '';
122
+ if (scanRelationId(name, detail) !== null || !isDeltaLogRead(name, detail)) return false;
123
+ scans++;
124
+ }
125
+ return scans > 0;
126
+ }
127
+
128
+ // ---- Alignment ------------------------------------------------------------------------------
129
+
130
+ export const PAIR_DELTA_METRICS = [
131
+ 'executorRunTime', 'executorCpuTime', 'memoryBytesSpilled', 'diskBytesSpilled',
132
+ 'inputBytes', 'outputBytes', 'shuffleReadBytes', 'shuffleWriteBytes',
133
+ ] ;
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+ /** The stages an aligned execution pair left unpaired when its two sides ran different numbers of
154
+ * stages: a different plan (a broadcast join in place of an exchange and a sort-merge join), not
155
+ * changed work. `deltas` totals each metric over the leftover stages of each side. */
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+ /** An execution pair scores at least this (0-1) on call site, description and plan structure to be
202
+ * matched. */
203
+ export const EXECUTION_MATCH_MIN = 0.55;
204
+ /** Share of the smaller run's executions that must pair (see `ExecutionAlignment.agreement`) for the two runs
205
+ * to count as one job. Below it no stage pairs: stages as generic as a `count` or a `collect` repeat
206
+ * across unrelated jobs, and only the surrounding executions tell the jobs apart. */
207
+ export const MIN_EXECUTION_AGREEMENT = 0.5;
208
+ /** Detail similarity (Jaccard over a stage's name and normalized node text) two stages of different
209
+ * structure need to pair as `aligned`. */
210
+ export const ALIGNED_MIN_SIMILARITY = 0.6;
211
+ /** Score of a pair made by position alone: no plan evidence backs it. */
212
+ export const POSITIONAL_SCORE = 0.5;
213
+ /** Execution similarities cached at most (distinct signatures in one run times those in the other). */
214
+ const MAX_CACHED_SIMILARITIES = 1_000_000;
215
+ /** Leftover stage pairs scored in one execution pair; above it the `aligned` pass is skipped. */
216
+ const MAX_STAGE_SIMILARITY_CELLS = 250_000;
217
+ /** Whether replanned run time counts in the numerator of `runtimeCoverage` (and so toward the `ok`
218
+ * gate). When false it counts in the denominator only. */
219
+ export const REPLANNED_COUNTS_TOWARD_COVERAGE = true;
220
+
221
+ // Sum of a field over the stage's own record and the work its figures leave out (earlier
222
+ // attempts, a failed attempt's late tasks), the same all-attempt sums the run totals use.
223
+ // null when no row carried a finite value, so an unrecorded field is not a false 0.
224
+ function attemptSum(rows , field ) {
225
+ let sum = 0, present = false;
226
+ for (const row of rows) {
227
+ const v = row[field];
228
+ if (typeof v === 'number' && Number.isFinite(v)) { sum += v; present = true; }
229
+ }
230
+ return present ? sum : null;
231
+ }
232
+
233
+
234
+
235
+ function stageFigures(stage ) {
236
+ const rows = withEarlierAttempts([stage]);
237
+ return {
238
+ executorRunTime: attemptSum(rows, 'executorRunTime'),
239
+ executorCpuTime: totalExecutorCpuMs(rows),
240
+ memoryBytesSpilled: attemptSum(rows, 'memoryBytesSpilled'),
241
+ diskBytesSpilled: attemptSum(rows, 'diskBytesSpilled'),
242
+ inputBytes: attemptSum(rows, 'inputBytes'),
243
+ outputBytes: attemptSum(rows, 'outputBytes'),
244
+ shuffleReadBytes: attemptSum(rows, 'shuffleReadBytes'),
245
+ shuffleWriteBytes: attemptSum(rows, 'shuffleWriteBytes'),
246
+ };
247
+ }
248
+
249
+ function deltasOf(b , c ) {
250
+ const deltas = {} ;
251
+ for (const metric of PAIR_DELTA_METRICS) {
252
+ const [bv, cv] = [b[metric], c[metric]];
253
+ deltas[metric] = { baseline: bv, candidate: cv, delta: bv != null && cv != null ? cv - bv : null };
254
+ }
255
+ return deltas;
256
+ }
257
+
258
+ /** Each metric summed over several stages' figures; null where no stage recorded it. */
259
+ function sumFigures(rows ) {
260
+ const total = {} ;
261
+ for (const metric of PAIR_DELTA_METRICS) {
262
+ let sum = 0, present = false;
263
+ for (const row of rows) if (row[metric] != null) { sum += row[metric] ; present = true; }
264
+ total[metric] = present ? sum : null;
265
+ }
266
+ return total;
267
+ }
268
+
269
+ const ascending = (a , b ) => a - b;
270
+
271
+ // ---- Per-run index --------------------------------------------------------------------------
272
+
273
+ // What the aligner knows about one SQL execution (or the pseudo-execution of stages outside any):
274
+ // the non-bookkeeping stages it ran and the three things its similarity reads.
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+ // Stack frames of the JVM and Spark themselves say nothing about which user code ran.
303
+ const FRAMEWORK_FRAME = /^(java|javax|jdk|sun|scala|py4j|org\.apache|io\.delta|io\.netty|com\.sun)\b/;
304
+
305
+ function userFrames(details ) {
306
+ const frames = [];
307
+ for (const line of (details ?? '').split('\n')) {
308
+ const frame = line.trim().replace(/:\d+\)$/, ')'); // a shifted line number is not a different call site
309
+ if (frame && !FRAMEWORK_FRAME.test(frame)) frames.push(frame);
310
+ }
311
+ return frames;
312
+ }
313
+
314
+ // Directory prefixes carry the run's own paths (a container directory, an application id), so only
315
+ // each path's last segment stays in a description.
316
+ const descriptionTokens = (text , normalizer ) =>
317
+ [...new Set(normalizer(text).replace(/\S*\/([^/\s]+)/g, '$1').split(/[^A-Za-z0-9_.$]+/).filter(Boolean))].sort();
318
+
319
+ // An execution's plan structure: its operator names (a repeated operator counts as often as it
320
+ // repeats) and the attribute names its nodes mention, which tell two plans of the same operators
321
+ // apart. Neither carries a literal, a path or an id.
322
+ function planStructure(root ) {
323
+ const tokens = [];
324
+ const attributes = new Set ();
325
+ (function walk(node ) {
326
+ if (!/^(WholeStageCodegen|InputAdapter|ColumnarToRow)\b/.test(node.name ?? '')) {
327
+ tokens.push(normalizeStageName(node.name ?? ''));
328
+ for (const name of attributeNames(node.detail ?? '')) attributes.add(`@${name}`);
329
+ }
330
+ for (const child of node.children ?? []) walk(child);
331
+ })(root);
332
+ return [...tokens, ...attributes].sort();
333
+ }
334
+
335
+ function indexRun(
336
+ snap , normalizer , signatures , interned ,
337
+ ) {
338
+ const intern = (tokens ) => {
339
+ const ids = new Int32Array(tokens.length);
340
+ tokens.forEach((t, i) => {
341
+ let id = interned.get(t);
342
+ if (id === undefined) { id = interned.size; interned.set(t, id); }
343
+ ids[i] = id;
344
+ });
345
+ return ids.sort();
346
+ };
347
+ const nodesOf = new Map ();
348
+ const bookkeeping = new Set ();
349
+ const exactKey = new Map ();
350
+ const structuralKeyOf = new Map ();
351
+ const byExecution = new Map ();
352
+ for (const [id, stage] of snap.stages) {
353
+ const nodes = planNodesOfStage(stage, snap.sql);
354
+ nodesOf.set(id, nodes);
355
+ if (isDeltaBookkeepingStage(stage, snap.sql, nodes)) { bookkeeping.add(id); continue; }
356
+ exactKey.set(id, comparisonIdentity(stage, snap, normalizer, nodes));
357
+ structuralKeyOf.set(id, structuralKey(nodes));
358
+ const execId = stage.sqlExecutionId ?? null;
359
+ const ids = byExecution.get(execId);
360
+ if (ids) ids.push(id); else byExecution.set(execId, [id]);
361
+ }
362
+ const info = (execId , stageIds ) => {
363
+ stageIds.sort(ascending);
364
+ const root = execId === null ? null : snap.sql.get(execId)?.planTree ?? null;
365
+ const structure = root ? planStructure(root)
366
+ : stageIds.map((id) => structuralKeyOf.get(id) ?? `n:${normalizeStageName(snap.stages.get(id) .name ?? '')}`).sort();
367
+ const frames = [...new Set(stageIds.flatMap((id) => userFrames(snap.stages.get(id) .details)))].sort();
368
+ const description = execId === null ? [] : descriptionTokens(snap.sql.get(execId)?.description ?? '', normalizer);
369
+ const text = JSON.stringify([structure, frames, description]);
370
+ let signature = signatures.get(text);
371
+ if (signature === undefined) { signature = signatures.size; signatures.set(text, signature); }
372
+ return { id: execId, stageIds, structure: intern(structure), frames: intern(frames), description: intern(description), signature };
373
+ };
374
+ const executions = [...byExecution.keys()].filter((k) => k !== null).sort(ascending)
375
+ .map((execId) => info(execId, byExecution.get(execId) ));
376
+ const pseudoIds = byExecution.get(null);
377
+ return {
378
+ snap, nodesOf, bookkeeping, exactKey, structuralKey: structuralKeyOf, executions,
379
+ pseudo: pseudoIds ? info(null, pseudoIds) : null,
380
+ };
381
+ }
382
+
383
+ // Weights of the three parts of an execution pair's similarity. A part one side lacks (a PySpark
384
+ // stack trace has no user frames, a description may be empty) drops out of the mean.
385
+ const STRUCTURE_WEIGHT = 4, FRAMES_WEIGHT = 2, DESCRIPTION_WEIGHT = 1;
386
+
387
+ /** 0-1000 (rounded, so the alignment compares integers). */
388
+ function executionSimilarity(a , b ) {
389
+ let sum = STRUCTURE_WEIGHT * jaccard(a.structure, b.structure), weight = STRUCTURE_WEIGHT;
390
+ if (a.frames.length > 0 && b.frames.length > 0) { sum += FRAMES_WEIGHT * jaccard(a.frames, b.frames); weight += FRAMES_WEIGHT; }
391
+ if (a.description.length > 0 && b.description.length > 0) {
392
+ sum += DESCRIPTION_WEIGHT * jaccard(a.description, b.description);
393
+ weight += DESCRIPTION_WEIGHT;
394
+ }
395
+ return Math.round((1000 * sum) / weight);
396
+ }
397
+
398
+ // ---- Stage alignment inside one execution pair -----------------------------------------------
399
+
400
+
401
+
402
+ function alignWithin(
403
+ base , cand , be , ce ,
404
+ tokens , normalizer ,
405
+ ) {
406
+ const pseudo = be.id === null;
407
+ const freeBase = new Set(be.stageIds), freeCand = new Set(ce.stageIds);
408
+ const pairs = [];
409
+ const take = (baseId , candId , quality , score ) => {
410
+ pairs.push({ baseId, candId, quality, score });
411
+ freeBase.delete(baseId);
412
+ freeCand.delete(candId);
413
+ };
414
+ const tokensOf = (run , cache , id ) => {
415
+ let t = cache.get(id);
416
+ if (!t) { const stage = run.snap.stages.get(id) ; t = detailTokens(stage.name, run.nodesOf.get(id) , normalizer); cache.set(id, t); }
417
+ return t;
418
+ };
419
+ const similarity = (baseId , candId ) =>
420
+ jaccard(tokensOf(base, tokens.base, baseId), tokensOf(cand, tokens.cand, candId));
421
+ const group = (free , keyOf ) => {
422
+ const groups = new Map ();
423
+ for (const id of [...free].sort(ascending)) {
424
+ const key = keyOf(id);
425
+ if (key === null) continue;
426
+ const ids = groups.get(key);
427
+ if (ids) ids.push(id); else groups.set(key, [id]);
428
+ }
429
+ return groups;
430
+ };
431
+ // Stages sharing a key pair off in id order, as many as both sides have: a self-join's subtree
432
+ // counted three times in one run and twice in the other still pairs two.
433
+ const pairByKey = (
434
+ keyOfBase , keyOfCand ,
435
+ quality , scoreOf ,
436
+ ) => {
437
+ const b = group(freeBase, keyOfBase), c = group(freeCand, keyOfCand);
438
+ for (const key of [...b.keys()].sort()) {
439
+ const candIds = c.get(key);
440
+ if (!candIds) continue;
441
+ const baseIds = b.get(key) ;
442
+ for (let i = 0; i < Math.min(baseIds.length, candIds.length); i++) take(baseIds[i], candIds[i], quality, scoreOf(baseIds[i], candIds[i]));
443
+ }
444
+ };
445
+
446
+ // Stages outside any SQL execution have no plan: the stage's own call-site text tells them apart.
447
+ const detailsKey = (run , id ) => normalizeStageName(normalizer(run.snap.stages.get(id) .details ?? ''));
448
+ pairByKey(
449
+ (id) => base.exactKey.get(id) + (pseudo ? `§${detailsKey(base, id)}` : ''),
450
+ (id) => cand.exactKey.get(id) + (pseudo ? `§${detailsKey(cand, id)}` : ''),
451
+ 'exact', () => 1,
452
+ );
453
+ pairByKey((id) => base.structuralKey.get(id) ?? null, (id) => cand.structuralKey.get(id) ?? null, 'structural', similarity);
454
+
455
+ // Different structure, similar details: best score first, ties by lower ids.
456
+ const withStructure = (run , free ) => [...free].filter((id) => run.structuralKey.get(id) != null).sort(ascending);
457
+ const leftBase = withStructure(base, freeBase), leftCand = withStructure(cand, freeCand);
458
+ if (leftBase.length * leftCand.length <= MAX_STAGE_SIMILARITY_CELLS) {
459
+ const candidates = [];
460
+ for (const baseId of leftBase) for (const candId of leftCand) {
461
+ const score = similarity(baseId, candId);
462
+ if (score >= ALIGNED_MIN_SIMILARITY) candidates.push({ baseId, candId, score });
463
+ }
464
+ candidates.sort((x, y) => y.score - x.score || x.baseId + x.candId - (y.baseId + y.candId) || x.baseId - y.baseId);
465
+ for (const { baseId, candId, score } of candidates) {
466
+ if (freeBase.has(baseId) && freeCand.has(candId)) take(baseId, candId, 'aligned', score);
467
+ }
468
+ }
469
+
470
+ // No plan structure to compare: pair by position among the stages of the same name.
471
+ const byName = (run ) => (id ) =>
472
+ run.structuralKey.get(id) == null ? normalizeStageName(run.snap.stages.get(id) .name ?? '') : null;
473
+ pairByKey(byName(base), byName(cand), 'aligned', () => POSITIONAL_SCORE);
474
+
475
+ return { pairs, leftBase: [...freeBase].sort(ascending), leftCand: [...freeCand].sort(ascending) };
476
+ }
477
+
478
+ /** Pairs the stages of two runs in two levels. SQL executions align first, in submission order, by an
479
+ * order-preserving sequence alignment scored on call site, description and plan structure (the
480
+ * stages outside any execution form one pseudo-execution per run); then the stages of each aligned
481
+ * execution pair align: equal comparison key (`exact`), equal structural key (`structural`), similar
482
+ * details (`aligned`), or position among plan-less stages of one name (`aligned`). Stages an
483
+ * execution pair with different stage counts leaves over are `replanned`; with equal counts they
484
+ * stay `unmatched`. Deterministic, and swapping the runs mirrors the pairs. */
485
+ export function alignStages(baseSnap , candSnap , options = {}) {
486
+ const normalizer = comparisonDetailNormalizer(options.normalizePath);
487
+ const signatures = new Map ();
488
+ const interned = new Map ();
489
+ const base = indexRun(baseSnap, normalizer, signatures, interned), cand = indexRun(candSnap, normalizer, signatures, interned);
490
+
491
+ // Execution similarity is cached per signature pair, so a loop of identical queries scores once.
492
+ // When the runs hold so many distinct signatures that the cache could not repay its memory, it is off.
493
+ const distinct = (run ) => new Set(run.executions.map((e) => e.signature)).size;
494
+ const cached = distinct(base) * distinct(cand) <= MAX_CACHED_SIMILARITIES;
495
+ const cache = new Map ();
496
+ const similarityOf = (a , b ) => {
497
+ if (a.signature === b.signature) return 1000;
498
+ if (!cached) return executionSimilarity(a, b);
499
+ const key = a.signature * signatures.size + b.signature;
500
+ let s = cache.get(key);
501
+ if (s === undefined) { s = executionSimilarity(a, b); cache.set(key, s); }
502
+ return s;
503
+ };
504
+ const sequence = alignSequences(
505
+ base.executions.length, cand.executions.length,
506
+ (i, j) => similarityOf(base.executions[i], cand.executions[j]), Math.round(1000 * EXECUTION_MATCH_MIN),
507
+ (i) => base.executions[i].id , (j) => cand.executions[j].id ,
508
+ );
509
+ const smaller = Math.min(base.executions.length, cand.executions.length);
510
+ const agreement = smaller > 0 ? sequence.pairs.length / smaller : null;
511
+ const accepted = agreement === null || agreement >= MIN_EXECUTION_AGREEMENT;
512
+
513
+ const executionPairs = [];
514
+ if (accepted) {
515
+ for (const [i, j] of sequence.pairs) executionPairs.push([base.executions[i], cand.executions[j]]);
516
+ if (base.pseudo && cand.pseudo) executionPairs.push([base.pseudo, cand.pseudo]);
517
+ }
518
+
519
+ const baseFigures = new Map (), candFigures = new Map ();
520
+ const figures = (cache , snap , id ) => {
521
+ let f = cache.get(id);
522
+ if (!f) { f = stageFigures(snap.stages.get(id) ); cache.set(id, f); }
523
+ return f;
524
+ };
525
+ const figuresOf = (side , ids ) =>
526
+ ids.map((id) => side === 'base' ? figures(baseFigures, baseSnap, id) : figures(candFigures, candSnap, id));
527
+
528
+ const tokens = { base: new Map (), cand: new Map () };
529
+ const pairs = [];
530
+ const replanned = [];
531
+ for (const [be, ce] of executionPairs) {
532
+ const result = alignWithin(base, cand, be, ce, tokens, normalizer);
533
+ for (const { baseId, candId, quality, score } of result.pairs) {
534
+ pairs.push({
535
+ pairId: `b${baseId}-c${candId}`, baseStageIds: [baseId], candStageIds: [candId], quality, score,
536
+ deltas: deltasOf(figures(baseFigures, baseSnap, baseId), figures(candFigures, candSnap, candId)),
537
+ });
538
+ }
539
+ // Different stage counts under one aligned execution: the plan changed, not the work. The
540
+ // pseudo-execution has no plan to re-plan, so its leftovers stay unmatched.
541
+ const leftover = result.leftBase.length + result.leftCand.length > 0;
542
+ if (leftover && be.id !== null && ce.id !== null && be.stageIds.length !== ce.stageIds.length) {
543
+ replanned.push({
544
+ baseExecutionId: be.id, candExecutionId: ce.id, baseStageIds: result.leftBase, candStageIds: result.leftCand,
545
+ deltas: deltasOf(sumFigures(figuresOf('base', result.leftBase)), sumFigures(figuresOf('cand', result.leftCand))),
546
+ });
547
+ }
548
+ }
549
+ pairs.sort((x, y) => x.baseStageIds[0] - y.baseStageIds[0] || x.candStageIds[0] - y.candStageIds[0]);
550
+ replanned.sort((x, y) => x.baseExecutionId - y.baseExecutionId || x.candExecutionId - y.candExecutionId);
551
+
552
+ const pairedBase = new Set(pairs.flatMap((p) => p.baseStageIds));
553
+ const pairedCand = new Set(pairs.flatMap((p) => p.candStageIds));
554
+ const replannedBase = new Set(replanned.flatMap((g) => g.baseStageIds));
555
+ const replannedCand = new Set(replanned.flatMap((g) => g.candStageIds));
556
+ const unmatchedOf = (snap , book , paired , replannedIds ) =>
557
+ [...snap.stages.keys()].filter((id) => !book.has(id) && !paired.has(id) && !replannedIds.has(id)).sort(ascending);
558
+
559
+ // Run time counts every attempt, the same figure the stage-pair deltas and the run totals use.
560
+ let total = 0, matched = 0;
561
+ const tally = (cache , snap , book , paired , replannedIds ) => {
562
+ for (const id of snap.stages.keys()) {
563
+ if (book.has(id)) continue;
564
+ const t = figures(cache, snap, id).executorRunTime ?? 0;
565
+ total += t;
566
+ if (paired.has(id) || (REPLANNED_COUNTS_TOWARD_COVERAGE && replannedIds.has(id))) matched += t;
567
+ }
568
+ };
569
+ tally(baseFigures, baseSnap, base.bookkeeping, pairedBase, replannedBase);
570
+ tally(candFigures, candSnap, cand.bookkeeping, pairedCand, replannedCand);
571
+
572
+ return {
573
+ pairs,
574
+ unmatched: {
575
+ baseStageIds: unmatchedOf(baseSnap, base.bookkeeping, pairedBase, replannedBase),
576
+ candStageIds: unmatchedOf(candSnap, cand.bookkeeping, pairedCand, replannedCand),
577
+ },
578
+ replanned,
579
+ bookkeepingStageIds: { baseStageIds: [...base.bookkeeping].sort(ascending), candStageIds: [...cand.bookkeeping].sort(ascending) },
580
+ runtimeCoverage: total > 0 ? matched / total : null,
581
+ executionAlignment: {
582
+ baseExecutions: base.executions.length, candExecutions: cand.executions.length,
583
+ pairedExecutions: sequence.pairs.length, bounded: sequence.bounded, agreement, accepted,
584
+ },
585
+ };
586
+ }