sparkforensics-server 0.6.0 → 0.8.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.
Files changed (410) hide show
  1. package/index.js +16 -7
  2. package/package.json +1 -1
  3. package/public/assets/AutoscalingChurn-DenWKTv-.js +1 -0
  4. package/public/assets/CacheUtilization-DLz7NdjV.js +1 -0
  5. package/public/assets/CachingOpportunity-zpQIqhzm.js +1 -0
  6. package/public/assets/ChartTheme-DAiARbLP.js +8 -0
  7. package/public/assets/ColdStart-C-9HjxWl.js +1 -0
  8. package/public/assets/ConfigAudit-D0V9tcrJ.js +1 -0
  9. package/public/assets/CoreUsageArea-yeerrljd.js +1 -0
  10. package/public/assets/Dashboard-DkUPrHKV.js +1 -0
  11. package/public/assets/DisclosureContext-DK1_iz6J.js +1 -0
  12. package/public/assets/DuplicatePlanSubtree-xDNDbCd3.js +1 -0
  13. package/public/assets/DurationHistogram-B6jsE0Ov.js +1 -0
  14. package/public/assets/EmptyState-C1uL40VU.js +1 -0
  15. package/public/assets/ExecutorUtilization-DXDZKmSc.js +1 -0
  16. package/public/assets/GcPressure-MRf7I4IG.js +1 -0
  17. package/public/assets/ImpactEstimate-Dm3xWbzV.js +1 -0
  18. package/public/assets/IncompleteRun-CaLOhMtf.js +1 -0
  19. package/public/assets/JobFailures-CzmzlvhO.js +1 -0
  20. package/public/assets/MemoryUtilization-AWUMUjCH.js +1 -0
  21. package/public/assets/NestedLoopJoin-C9sNr6Ss.js +1 -0
  22. package/public/assets/OverBroadcast-BOmRE6ca.js +1 -0
  23. package/public/assets/PartitionHint-BL2EmJfE.js +1 -0
  24. package/public/assets/PartitionSizing-BzBySvUq.js +1 -0
  25. package/public/assets/PlanExplorer-DdiQRcJU.js +1 -0
  26. package/public/assets/PlanGraphRoute-CIH9gBuR.js +1 -0
  27. package/public/assets/PlanView-B1hxzFRs.js +2 -0
  28. package/public/assets/PythonUdf-DvgCCueD.js +1 -0
  29. package/public/assets/RetryWaste-uXXu_CyM.js +1 -0
  30. package/public/assets/RowPagination-C-LzWkI5.js +1 -0
  31. package/public/assets/RowStatusCluster-Dbtmte7l.js +1 -0
  32. package/public/assets/ShuffleIO-nALwzC1_.js +1 -0
  33. package/public/assets/Skew-CHMEKgvk.js +1 -0
  34. package/public/assets/SlowHost-yyvjpHk6.js +1 -0
  35. package/public/assets/SmallFiles-y6odJ97n.js +1 -0
  36. package/public/assets/SpeculationWaste-C-SD2LOt.js +1 -0
  37. package/public/assets/Spill-DHTMmOup.js +1 -0
  38. package/public/assets/StageFailed-CQ3DxC9R.js +1 -0
  39. package/public/assets/StageFindingGroup-BgYLyj5q.js +1 -0
  40. package/public/assets/StageHeader-CIETkjVZ.js +1 -0
  41. package/public/assets/StagePill--P7PDd3s.js +1 -0
  42. package/public/assets/StageShape-BIv8vx_o.js +1 -0
  43. package/public/assets/StageSlowness-MMqzERxN.js +1 -0
  44. package/public/assets/Straggler-BgrY7AyW.js +1 -0
  45. package/public/assets/TaskFailures-BHHyKYTs.js +1 -0
  46. package/public/assets/TinyTask-DDScNhCl.js +1 -0
  47. package/public/assets/UnderBroadcast-CGIC04TI.js +1 -0
  48. package/public/assets/WidgetLeadSummary-CXFZP00C.js +1 -0
  49. package/public/assets/button-DbpTz2yk.js +1 -0
  50. package/public/assets/charts-vendor-CtTxwTeu.js +59 -0
  51. package/public/assets/composite-C9Nu2EI9.js +1 -0
  52. package/public/assets/d3-vendor-DNlD2-cz.js +1 -0
  53. package/public/assets/detector-registry-Bp8hrLua.js +2 -0
  54. package/public/assets/downsample-WmNM1qc9.js +1 -0
  55. package/public/assets/finding-anchor-Dr40LkS9.js +1 -0
  56. package/public/assets/findings-of-type-BL2u3RGZ.js +1 -0
  57. package/public/assets/impact-sort-mC5ISgX8.js +1 -0
  58. package/public/assets/inbox-KPU1Gppm.js +1 -0
  59. package/public/assets/index-CaRVPvAT.css +2 -0
  60. package/public/assets/index-fDiwfuoO.js +14 -0
  61. package/public/assets/inertValue-L9VxV0VE.js +1 -0
  62. package/public/assets/layout.worker-CLcBHxVp.js +3 -0
  63. package/public/assets/{parser-worker-CA5RxqKc.js → parser-worker-CRIOPMOW.js} +8 -8
  64. package/public/assets/plan-graph-vendor-Ce6d4HU6.js +7 -0
  65. package/public/assets/react-vendor-C6KcUD36.js +9 -0
  66. package/public/assets/select-BHqOqun5.js +1 -0
  67. package/public/assets/toggle-DPeN6aNa.js +1 -0
  68. package/public/assets/toggle-group-Kl3uyHSr.js +1 -0
  69. package/public/assets/usePagedRows-BafD6bEN.js +1 -0
  70. package/public/assets/useRecentFiles-DzJZAoOT.js +3 -0
  71. package/public/assets/useRegisterFieldControl-pQv-ZDoU.js +1 -0
  72. package/public/assets/useScrollLock-BCxn_dMp.js +1 -0
  73. package/public/assets/useSortMode-DjrAN-z4.js +1 -0
  74. package/public/docs/404.html +3 -3
  75. package/public/docs/assets/{app.CnWL6bKV.js → app.1gvzd5Zb.js} +1 -1
  76. package/public/docs/assets/chunks/@localSearchIndexroot.DS2TQP5_.js +1 -0
  77. package/public/docs/assets/chunks/{VPLocalSearchBox.C0IBKeRQ.js → VPLocalSearchBox.CNxT3s0I.js} +1 -1
  78. package/public/docs/assets/chunks/{theme.BH-2HuRf.js → theme.C5lLqUv7.js} +2 -2
  79. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.lean.js +1 -0
  81. package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.js} +1 -1
  82. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.js +1 -0
  83. package/public/docs/assets/{contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.js → contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.js} +1 -1
  84. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CHHgywoP.js +1 -0
  86. package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.N4hhenrA.lean.js → contributor-guide_architecture_detector-contract.md.CHHgywoP.lean.js} +1 -1
  87. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.js +1 -0
  90. package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.lean.js} +1 -1
  91. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.js} +1 -1
  92. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.lean.js} +1 -1
  93. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.js +1 -0
  94. package/public/docs/assets/{contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js → contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.lean.js} +1 -1
  95. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.K71YB-Rr.js +1 -0
  96. package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.DfvbqEUu.lean.js → contributor-guide_architecture_impact-estimation.md.K71YB-Rr.lean.js} +1 -1
  97. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.js +1 -0
  98. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.lean.js} +1 -1
  99. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_overview.md.nxkpOhgf.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.js +1 -0
  103. package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.lean.js} +1 -1
  104. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.lean.js +1 -0
  107. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.js} +1 -1
  108. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.js} +1 -1
  109. package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js → contributor-guide_architecture_worker-protocol.md.Br6OE7RW.js} +1 -1
  110. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.Br6OE7RW.lean.js +1 -0
  111. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.js +1 -0
  112. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.lean.js} +1 -1
  113. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.js} +1 -1
  114. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.js} +2 -2
  115. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.lean.js} +1 -1
  116. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.js +1 -0
  117. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.lean.js} +1 -1
  118. package/public/docs/assets/contributor-guide_contributing.md.DBW4drtZ.js +1 -0
  119. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.js +15 -0
  120. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.lean.js +1 -0
  121. package/public/docs/assets/contributor-guide_testing.md.DpkozNoG.js +4 -0
  122. package/public/docs/assets/{contributor-guide_testing.md.By6XGR9a.lean.js → contributor-guide_testing.md.DpkozNoG.lean.js} +1 -1
  123. package/public/docs/assets/{index.md.CHJVslga.js → index.md.lMm7SUMz.js} +1 -1
  124. package/public/docs/assets/{index.md.CHJVslga.lean.js → index.md.lMm7SUMz.lean.js} +1 -1
  125. package/public/docs/assets/{style.CyK9Ss73.css → style.Cfxqlj0y.css} +1 -1
  126. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.js +29 -0
  127. package/public/docs/assets/{user-guide_getting-started_ci-and-automation.md.BEepbDDL.lean.js → user-guide_getting-started_ci-and-automation.md.C0pcQoNc.lean.js} +1 -1
  128. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.js +1 -0
  129. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.js} +2 -2
  130. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.lean.js} +1 -1
  131. package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.js → user-guide_mcp-tools.md.49r3L2YE.js} +1 -1
  132. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js → user-guide_mcp-tools_compare-runs.md.BQ_kIptC.js} +11 -6
  133. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.BQ_kIptC.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.azCZXkH8.js +44 -0
  135. package/public/docs/assets/{user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js → user-guide_mcp-tools_diagnose-run.md.azCZXkH8.lean.js} +1 -1
  136. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.js} +2 -2
  137. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.lean.js} +1 -1
  138. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.js} +2 -1
  139. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.lean.js} +1 -1
  140. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.js +1 -0
  141. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.lean.js +1 -0
  142. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.js +1 -0
  143. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.lean.js +1 -0
  144. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -6
  145. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +6 -6
  146. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -6
  147. package/public/docs/contributor-guide/architecture/board-widgets.html +6 -6
  148. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +6 -6
  149. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -6
  150. package/public/docs/contributor-guide/architecture/detector-contract.html +6 -6
  151. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +6 -6
  152. package/public/docs/contributor-guide/architecture/drill-down.html +6 -6
  153. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -6
  154. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +6 -6
  155. package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -6
  156. package/public/docs/contributor-guide/architecture/index.html +5 -5
  157. package/public/docs/contributor-guide/architecture/overview.html +6 -6
  158. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +5 -5
  159. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -6
  160. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  161. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +6 -6
  162. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +5 -5
  163. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +6 -6
  164. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +6 -6
  166. package/public/docs/contributor-guide/architecture/widget-rendering.html +6 -6
  167. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -6
  168. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +6 -6
  169. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +7 -7
  170. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -6
  171. package/public/docs/contributor-guide/architecture/worker-protocol.html +6 -6
  172. package/public/docs/contributor-guide/contributing.html +6 -6
  173. package/public/docs/contributor-guide/development-setup.html +8 -8
  174. package/public/docs/contributor-guide/testing.html +9 -6
  175. package/public/docs/hashmap.json +1 -1
  176. package/public/docs/index.html +6 -6
  177. package/public/docs/logo-dark.svg +1 -0
  178. package/public/docs/logo-light.svg +1 -0
  179. package/public/docs/tuning-reference/anti-patterns.html +5 -5
  180. package/public/docs/tuning-reference/aqe.html +5 -5
  181. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -5
  182. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -5
  183. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -5
  184. package/public/docs/tuning-reference/bottleneck-failures.html +5 -5
  185. package/public/docs/tuning-reference/bottleneck-gc.html +5 -5
  186. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -5
  187. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -5
  188. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -5
  189. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -5
  190. package/public/docs/tuning-reference/bottleneck-skew.html +5 -5
  191. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -5
  192. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -5
  193. package/public/docs/tuning-reference/bottleneck-spill.html +5 -5
  194. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -5
  195. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -5
  196. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -5
  197. package/public/docs/tuning-reference/caching.html +5 -5
  198. package/public/docs/tuning-reference/cluster-config.html +5 -5
  199. package/public/docs/tuning-reference/config.html +5 -5
  200. package/public/docs/tuning-reference/data-formats.html +5 -5
  201. package/public/docs/tuning-reference/index.html +5 -5
  202. package/public/docs/tuning-reference/intro.html +5 -5
  203. package/public/docs/tuning-reference/joins.html +5 -5
  204. package/public/docs/tuning-reference/memory-model.html +5 -5
  205. package/public/docs/tuning-reference/metrics.html +5 -5
  206. package/public/docs/tuning-reference/partitioning.html +5 -5
  207. package/public/docs/tuning-reference/pyspark.html +5 -5
  208. package/public/docs/tuning-reference/shuffle.html +5 -5
  209. package/public/docs/tuning-reference/spark-architecture.html +5 -5
  210. package/public/docs/tuning-reference/table-formats.html +5 -5
  211. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +5 -5
  212. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +5 -5
  213. package/public/docs/user-guide/alternative-log-retrieval.html +5 -5
  214. package/public/docs/user-guide/getting-started/ci-and-automation.html +16 -11
  215. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +6 -6
  216. package/public/docs/user-guide/getting-started/regression-metric-keys.html +5 -5
  217. package/public/docs/user-guide/getting-started/tuning-thresholds.html +7 -7
  218. package/public/docs/user-guide/getting-started.html +5 -5
  219. package/public/docs/user-guide/mcp-tools/compare-runs.html +16 -11
  220. package/public/docs/user-guide/mcp-tools/diagnose-run.html +28 -7
  221. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -6
  222. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +5 -5
  223. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +7 -6
  224. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +5 -5
  225. package/public/docs/user-guide/mcp-tools/get-run-summary.html +5 -5
  226. package/public/docs/user-guide/mcp-tools/list-runs.html +5 -5
  227. package/public/docs/user-guide/mcp-tools.html +6 -6
  228. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +5 -5
  229. package/public/docs/user-guide/run-comparison.html +6 -6
  230. package/public/docs/user-guide/understanding-findings.html +6 -6
  231. package/public/export-template.html +45 -45
  232. package/public/index.html +8 -7
  233. package/public/sample-runs/sample-run.ndjson.gz +0 -0
  234. package/vendor-core/allocation.js +25 -23
  235. package/vendor-core/analyzer.js +85 -58
  236. package/vendor-core/api-merge-writes.js +2 -0
  237. package/vendor-core/aqe-skew.js +347 -0
  238. package/vendor-core/cli/budgets.js +6 -4
  239. package/vendor-core/cli/collect-run-pair.js +89 -0
  240. package/vendor-core/cli/collect-run-worker.js +16 -0
  241. package/vendor-core/cli/collect-run.js +1 -2
  242. package/vendor-core/cli/native-zstd.js +12 -16
  243. package/vendor-core/comparability.js +234 -0
  244. package/vendor-core/comparison-output.js +15 -7
  245. package/vendor-core/comparison-verdict.js +105 -25
  246. package/vendor-core/core-source-hash.txt +1 -1
  247. package/vendor-core/core-time-series.js +0 -1
  248. package/vendor-core/core-usage-locality.js +1 -1
  249. package/vendor-core/detectors.js +1007 -380
  250. package/vendor-core/docs-config.js +0 -4
  251. package/vendor-core/docs-content/detection/cache.md +2 -1
  252. package/vendor-core/docs-content/detection/cfg.md +19 -11
  253. package/vendor-core/docs-content/detection/cold.md +2 -1
  254. package/vendor-core/docs-content/detection/gc.md +7 -3
  255. package/vendor-core/docs-content/detection/host.md +11 -1
  256. package/vendor-core/docs-content/detection/mem.md +17 -6
  257. package/vendor-core/docs-content/detection/part.md +3 -1
  258. package/vendor-core/docs-content/detection/plan.md +55 -13
  259. package/vendor-core/docs-content/detection/shfl.md +11 -5
  260. package/vendor-core/docs-content/detection/skew.md +47 -1
  261. package/vendor-core/docs-content/detection/slow.md +4 -2
  262. package/vendor-core/docs-content/detection/spec.md +6 -1
  263. package/vendor-core/docs-content/detection/spill.md +6 -3
  264. package/vendor-core/docs-content/detection/strag.md +19 -6
  265. package/vendor-core/docs-content/detection/tiny.md +9 -3
  266. package/vendor-core/effective-conf.js +7 -10
  267. package/vendor-core/efficiency-model.js +2 -2
  268. package/vendor-core/etl-phases.js +1 -2
  269. package/vendor-core/event-handlers.js +175 -57
  270. package/vendor-core/event-schemas.js +21 -16
  271. package/vendor-core/evidence-availability.js +9 -2
  272. package/vendor-core/evidence-report.js +118 -35
  273. package/vendor-core/executor-peaks.js +41 -0
  274. package/vendor-core/export-data.js +5 -5
  275. package/vendor-core/finding-identity.js +78 -0
  276. package/vendor-core/finding-presentation.js +116 -33
  277. package/vendor-core/finding-types.js +88 -21
  278. package/vendor-core/format-utils.js +14 -15
  279. package/vendor-core/html-export.js +1 -1
  280. package/vendor-core/impact-band.js +2 -2
  281. package/vendor-core/impact-format.js +47 -19
  282. package/vendor-core/impact-model.js +26 -15
  283. package/vendor-core/job-groups.js +4 -4
  284. package/vendor-core/mcp-server-factory.js +48 -12
  285. package/vendor-core/mcp-tools.js +179 -24
  286. package/vendor-core/median.js +2 -2
  287. package/vendor-core/nested-loop-join.js +73 -0
  288. package/vendor-core/occupancy.js +1 -2
  289. package/vendor-core/parser-worker.js +1 -1
  290. package/vendor-core/plan-graph-model.js +14 -22
  291. package/vendor-core/plan-node-detail.js +2 -3
  292. package/vendor-core/plan-summary.js +24 -19
  293. package/vendor-core/plan-tree-walk.js +6 -8
  294. package/vendor-core/python-stage.js +12 -1
  295. package/vendor-core/recommendation-rollup.js +13 -12
  296. package/vendor-core/redact.js +52 -46
  297. package/vendor-core/run-aggregates.js +6 -8
  298. package/vendor-core/run-comparison.js +226 -66
  299. package/vendor-core/run-identity.js +12 -0
  300. package/vendor-core/run-interpretation.js +21 -0
  301. package/vendor-core/run-metrics.js +2 -2
  302. package/vendor-core/run-output.js +32 -10
  303. package/vendor-core/run-totals.js +12 -0
  304. package/vendor-core/run-verdict.js +76 -3
  305. package/vendor-core/scaling-sim.js +3 -4
  306. package/vendor-core/snappy-block.js +2 -2
  307. package/vendor-core/spark-conf.js +168 -0
  308. package/vendor-core/sql-stages.js +2 -2
  309. package/vendor-core/stage-alignment.js +2 -12
  310. package/vendor-core/stage-identity.js +58 -3
  311. package/vendor-core/stage-label.js +14 -0
  312. package/vendor-core/stage-plan-nodes.js +17 -3
  313. package/vendor-core/stage-quantiles.js +141 -9
  314. package/vendor-core/stage-structure.js +17 -3
  315. package/vendor-core/threshold-overrides.js +9 -3
  316. package/vendor-core/types.js +58 -21
  317. package/vendor-core/wasted-core-hours.js +0 -1
  318. package/vendor-core/write-targets.js +114 -14
  319. package/vendor-core/zstd-worker-client.js +1 -1
  320. package/vendor-core/zstd-worker.js +1 -1
  321. package/public/assets/AutoscalingChurn-CRA2Mb7F.js +0 -1
  322. package/public/assets/CacheUtilization-Bh9OY_V9.js +0 -1
  323. package/public/assets/CachingOpportunity-oCiQwmTJ.js +0 -1
  324. package/public/assets/ColdStart-BJrtjfmn.js +0 -1
  325. package/public/assets/ConfigAudit-DMvKxySP.js +0 -1
  326. package/public/assets/CoreUsageArea-REC2zsKw.js +0 -1
  327. package/public/assets/Dashboard-BHZ76JCu.js +0 -2
  328. package/public/assets/DisclosureContext-OO7X1HAx.js +0 -1
  329. package/public/assets/DuplicatePlanSubtree-BgG-L-cK.js +0 -1
  330. package/public/assets/ExecutorUtilization-D2KhSVbY.js +0 -1
  331. package/public/assets/GcPressure-D1BTE8qh.js +0 -1
  332. package/public/assets/IncompleteRun-DD40xUpy.js +0 -1
  333. package/public/assets/JobFailures-YwNzCF19.js +0 -1
  334. package/public/assets/MemoryUtilization-9b8ee_3_.js +0 -1
  335. package/public/assets/OverBroadcast-CfctS8a8.js +0 -1
  336. package/public/assets/PartitionHint-CUvbnnxy.js +0 -1
  337. package/public/assets/PartitionSizing-VG8kBgG5.js +0 -1
  338. package/public/assets/PlanExplorer-DmubTAOP.js +0 -1
  339. package/public/assets/PlanGraphRoute-DctPZjS0.js +0 -1
  340. package/public/assets/RetryWaste-CnpM8bB7.js +0 -1
  341. package/public/assets/ShuffleIO-D13vGmas.js +0 -1
  342. package/public/assets/Skew-DlagstYP.js +0 -1
  343. package/public/assets/SlowHost-cSkLRZ_e.js +0 -1
  344. package/public/assets/SmallFiles-B-kERMcq.js +0 -1
  345. package/public/assets/SpeculationWaste-DaysAzXO.js +0 -1
  346. package/public/assets/Spill-H44Gz4US.js +0 -1
  347. package/public/assets/StageFailed-DCfii9h_.js +0 -1
  348. package/public/assets/StageFindingGroup-BXJ0Mj5n.js +0 -1
  349. package/public/assets/StageHeader--oN2J-oD.js +0 -1
  350. package/public/assets/StageShape-IZ80CZiJ.js +0 -1
  351. package/public/assets/StageSlowness--BRIaBc1.js +0 -1
  352. package/public/assets/Straggler-Ctq0Df_o.js +0 -1
  353. package/public/assets/TaskFailures-Bks_gqhL.js +0 -1
  354. package/public/assets/TinyTask-BssfzGFB.js +0 -1
  355. package/public/assets/UnderBroadcast-DsScrxc-.js +0 -1
  356. package/public/assets/charts-vendor-0VMaLVLN.js +0 -59
  357. package/public/assets/finding-anchor-BYW1oz5R.js +0 -1
  358. package/public/assets/impact-sort-xzy0g-Vh.js +0 -1
  359. package/public/assets/index-DhpRGMZc.css +0 -2
  360. package/public/assets/index-DwqkFUFk.js +0 -14
  361. package/public/assets/plan-graph-vendor-BYl-AzN5.js +0 -7
  362. package/public/assets/react-vendor-DfpuPQZf.js +0 -9
  363. package/public/assets/select-C8edjYTq.js +0 -1
  364. package/public/assets/usePagedRows-CsuUFeYx.js +0 -1
  365. package/public/assets/useRecentFiles-BvkKo3JN.js +0 -11
  366. package/public/assets/useSortMode-joX71BZN.js +0 -1
  367. package/public/docs/assets/chunks/@localSearchIndexroot.tE5pz54z.js +0 -1
  368. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +0 -1
  369. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +0 -1
  370. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +0 -1
  371. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.lean.js +0 -1
  372. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.N4hhenrA.js +0 -1
  373. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +0 -1
  374. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +0 -1
  375. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.js +0 -1
  376. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +0 -1
  377. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.DfvbqEUu.js +0 -1
  378. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.js +0 -1
  379. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +0 -1
  380. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +0 -1
  381. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +0 -1
  382. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +0 -1
  383. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +0 -1
  384. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +0 -1
  385. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +0 -1
  386. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +0 -1
  387. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.js +0 -1
  388. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.js +0 -1
  389. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +0 -1
  390. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.js +0 -15
  391. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.lean.js +0 -1
  392. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +0 -1
  393. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.js +0 -24
  394. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +0 -1
  395. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +0 -1
  396. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +0 -23
  397. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +0 -1
  398. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +0 -1
  399. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.js +0 -1
  400. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.lean.js +0 -1
  401. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.lean.js} +0 -0
  402. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js → contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.lean.js} +0 -0
  403. /package/public/docs/assets/{contributor-guide_architecture_overview.md.C1sSvm9i.lean.js → contributor-guide_architecture_overview.md.nxkpOhgf.lean.js} +0 -0
  404. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js → contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.lean.js} +0 -0
  405. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.lean.js} +0 -0
  406. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.lean.js} +0 -0
  407. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.lean.js} +0 -0
  408. /package/public/docs/assets/{contributor-guide_contributing.md.CQMvGwFt.lean.js → contributor-guide_contributing.md.DBW4drtZ.lean.js} +0 -0
  409. /package/public/docs/assets/{user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js → user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.lean.js} +0 -0
  410. /package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.lean.js → user-guide_mcp-tools.md.49r3L2YE.lean.js} +0 -0
@@ -6,41 +6,79 @@ import { totalExecutorCpuMs, withEarlierAttempts } from './run-totals.js';
6
6
  import { captureSnapshot } from './session-snapshot.js';
7
7
  import { alignStages, compileNormalizePatterns, COMPARISON_SCHEMA_VERSION, } from './stage-alignment.js';
8
8
  import { tunedRunNote } from './threshold-overrides.js';
9
-
9
+ import { diffFindings, findingBand, findingKey, findingRule, } from './finding-identity.js';
10
+ import { cyrb53 } from './string-hash.js';
11
+ import { IMPACT_BAND_ORDER } from './format-utils.js';
12
+
10
13
 
11
14
 
12
15
  import { isIncompleteRun } from './check-coverage.js';
16
+ import { stageDisplayLabel } from './stage-label.js';
17
+ import { runAppIdentity, } from './run-identity.js';
13
18
  import { summarizeRunOutcome } from './run-outcome.js';
19
+ import { buildComparability, withinBand, } from './comparability.js';
14
20
 
15
21
 
16
22
 
17
23
 
18
24
 
19
25
 
26
+
27
+
28
+
20
29
 
30
+ // An introduced or resolved finding category: the findings of one rule and band that have no
31
+ // counterpart in the other run. An introduced row has `baseCount` 0, a resolved row `candCount` 0.
32
+ // `keys`: the `key` of each finding the row counts, as `diagnose_run` reports it for the run the
33
+ // findings are in (the candidate for an introduced row, the baseline for a resolved one).
21
34
 
22
35
 
36
+
37
+
38
+
39
+
40
+ // Findings both runs have, at one location, that moved from one impact band to another. `count`
41
+ // is how many findings of that rule moved between those two bands.
42
+ // `keys` are the candidate run's finding keys, `baseKeys` the baseline's: a pair the aligner matched
43
+ // without an exact fingerprint match has different keys on the two sides.
44
+
45
+
46
+
47
+
48
+
49
+
50
+ /** True when a changed finding moved to a more severe band (an unknown band ranks least severe). */
51
+ export function isEscalation(row ) {
52
+ const rank = (band ) => IMPACT_BAND_ORDER[band ] ?? 9;
53
+ return rank(row.candImpactBand) < rank(row.baseImpactBand);
54
+ }
55
+
56
+
23
57
 
24
58
 
25
59
 
26
60
 
27
61
 
62
+ /** A stage pair with its stage name as a display label (call-site directories removed). The pair
63
+ * carries no raw name, so the label is the only name on the row. */
64
+
28
65
 
29
66
 
67
+
30
68
 
31
69
 
32
-
70
+
33
71
 
34
72
 
35
73
 
36
74
 
37
-
75
+
38
76
 
39
77
 
40
78
 
41
79
 
42
80
 
43
-
81
+
44
82
 
45
83
 
46
84
 
@@ -48,6 +86,7 @@ import { summarizeRunOutcome } from './run-outcome.js';
48
86
 
49
87
 
50
88
 
89
+
51
90
 
52
91
 
53
92
  function jobOutcome(snapshot ) {
@@ -61,6 +100,41 @@ export function stageIdentity(stage , snapshot
61
100
  return stageIdentityWith(stage, snapshot, normalizeDetail);
62
101
  }
63
102
 
103
+ /** The key a finding row carries (`findingKey`: type, rule and location) with the location read off
104
+ * the run alone: a stage by its `stageIdentity` fingerprint (the keys of `metrics.stages`), an SQL
105
+ * execution by a digest of its stages' fingerprints. Two runs of the same job give the same finding
106
+ * the same key, so a reader pairs findings across runs without the aligner. A pairing aid: several
107
+ * findings can share a key (one detector firing per host on a run-level key, stages that share a
108
+ * fingerprint), and it says nothing about whether the finding's value or band held. */
109
+ export function findingKeyer(snapshot ) {
110
+ const stageLabels = new Map ();
111
+ const stageLabel = (id ) => {
112
+ let label = stageLabels.get(id);
113
+ if (label === undefined) {
114
+ const stage = snapshot.stages.get(id);
115
+ label = stage ? stageIdentity(stage, snapshot) : `#${id}`;
116
+ stageLabels.set(id, label);
117
+ }
118
+ return label;
119
+ };
120
+ let stagesByExecution ;
121
+ const executionLabel = (id ) => {
122
+ if (!stagesByExecution) {
123
+ stagesByExecution = new Map();
124
+ for (const [stageId, stage] of snapshot.stages) {
125
+ if (stage.sqlExecutionId == null) continue;
126
+ const ids = stagesByExecution.get(stage.sqlExecutionId);
127
+ if (ids) ids.push(stageId); else stagesByExecution.set(stage.sqlExecutionId, [stageId]);
128
+ }
129
+ }
130
+ const ids = stagesByExecution.get(id);
131
+ // An execution with no stage has no fingerprint to digest: its id is all that names it.
132
+ return ids ? cyrb53(JSON.stringify(ids.map(stageLabel).sort())) : `#${id}`;
133
+ };
134
+ const locator = { stage: stageLabel, execution: executionLabel };
135
+ return (finding) => findingKey(finding, locator);
136
+ }
137
+
64
138
  export function matchStages(baseSnap , candSnap )
65
139
 
66
140
 
@@ -135,13 +209,9 @@ export const COMPARISON_METRIC_KEYS = [
135
209
  'executorCpuTime', 'allocatedCoreHours',
136
210
  ];
137
211
 
138
- // Volume/count metrics, not cost metrics: more or less input/output data, or
139
- // tasks/executors, isn't inherently better or worse (it may just reflect a
140
- // differently-sized job), unlike wall-clock, spill, GC, etc. Exported as the
141
- // single source of truth for "which metrics have no better/worse direction",
142
- // shared by the view (RunComparison.tsx, for Δ coloring) and by
143
- // src/cli/budgets.ts's checkRegression (so a `--regression-metric inputBytes`
144
- // budget can't misread "processed more data" as a regression).
212
+ // Volume/count metrics have no better/worse direction: more input, output, tasks or executors may
213
+ // just reflect a differently-sized job. Shared by the view (Δ coloring) and by the CLI's
214
+ // checkRegression, so a `--regression-metric inputBytes` budget cannot read more data as a regression.
145
215
  export const NEUTRAL_METRIC_KEYS = new Set(['inputBytes', 'outputBytes', 'taskCount', 'executorsAdded']);
146
216
 
147
217
  function direction(key , baseline , candidate ) {
@@ -151,16 +221,21 @@ function direction(key , baseline , candidate
151
221
  return d < 0 ? 'improvement' : d > 0 ? 'regression' : 'unchanged';
152
222
  }
153
223
 
154
- function metric(
155
- key , label , baseline , candidate ,
156
- extra = {},
157
- ) {
158
- const dir = direction(key, baseline, candidate);
159
- const delta = baseline == null || candidate == null ? null : candidate - baseline;
160
- return { key, label, baseline, candidate, delta, direction: dir, ...extra };
161
- }
224
+ /** A change under this share of the baseline's value is within run-to-run noise: a shared cluster
225
+ * easily moves a job a percent or two. The caller's `noiseBand` replaces it. */
226
+ export const DEFAULT_NOISE_BAND = 0.02;
162
227
 
163
- export function metricDeltas(baseSnap , candSnap ) {
228
+ export function metricDeltas(baseSnap , candSnap , noiseBand = DEFAULT_NOISE_BAND) {
229
+ const metric = (
230
+ key , label , baseline , candidate ,
231
+ extra = {},
232
+ ) => {
233
+ const delta = baseline == null || candidate == null ? null : candidate - baseline;
234
+ return {
235
+ key, label, baseline, candidate, delta, direction: direction(key, baseline, candidate),
236
+ withinNoise: baseline != null && candidate != null && withinBand(baseline, candidate, noiseBand), ...extra,
237
+ };
238
+ };
164
239
  const out = [];
165
240
  // Run totals count every task attempt, failed and speculative ones too (run-totals.ts), the
166
241
  // same sums the CLI metrics block reports; skew stays on each stage's latest attempt.
@@ -238,44 +313,84 @@ export function metricDeltas(baseSnap , candSnap
238
313
  return out;
239
314
  }
240
315
 
241
- // Finding categories are counted, not matched: a (rule × impact band) tally needs
242
- // no cross-run stage identity, so it stays correct even when almost no stages
243
- // match uniquely (the common case on real logs, see plan rationale). A
244
- // category the candidate has more of is "introduced"; fewer, "resolved".
245
- export function findingsDelta(baseSnap , candSnap )
246
-
247
- {
248
-
249
- const tally = (snap ) => {
250
- const m = new Map (); // `${rule}§${impactBand}` -> { rule, impactBand, count, stages:Set }
251
- for (const f of snap.catalog) {
252
- const rule = 'rule' in f && typeof f.rule === 'string' ? f.rule : f.type;
253
- const impactBand = f.impactBand ?? 'unknown';
254
- const key = `${rule}§${impactBand}`;
255
- const e = m.get(key) ?? { rule, type: f.type, impactBand, count: 0, stages: new Set () };
256
- e.count++;
257
- // Resolve stageId → name on this snapshot only: a single-side lookup, so
258
- // it needs no cross-run identity. App-level findings (stageId null) add none.
259
- const stage = f.stageId != null ? snap.stages.get(f.stageId) : null;
260
- if (stage?.name) e.stages.add(stage.name);
261
- m.set(key, e);
316
+ // Cross-run identity of a snapshot's stages and SQL executions: a stage (or execution) the aligner
317
+ // paired takes one label on both sides; an unpaired one takes a side-neutral label, so the same problem on an unpaired stage in both runs still matches.
318
+ function findingLocators(baseSnap , candSnap , stagePairs ) {
319
+ const baseStage = new Map (), candStage = new Map ();
320
+ const executionVotes = new Map (); // base execution -> cand execution -> paired stages
321
+ for (const pair of stagePairs) {
322
+ baseStage.set(pair.baseStageIds[0], pair.pairId);
323
+ candStage.set(pair.candStageIds[0], pair.pairId);
324
+ const baseExec = baseSnap.stages.get(pair.baseStageIds[0])?.sqlExecutionId;
325
+ const candExec = candSnap.stages.get(pair.candStageIds[0])?.sqlExecutionId;
326
+ if (baseExec == null || candExec == null) continue;
327
+ const votes = executionVotes.get(baseExec) ?? new Map ();
328
+ votes.set(candExec, (votes.get(candExec) ?? 0) + 1);
329
+ executionVotes.set(baseExec, votes);
330
+ }
331
+ // An execution pairs with the candidate execution that holds most of its paired stages.
332
+ const executionPair = new Map ();
333
+ for (const [baseExec, votes] of executionVotes) {
334
+ const [candExec] = [...votes].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0];
335
+ executionPair.set(baseExec, candExec);
336
+ }
337
+ const pairedCandExecs = new Set(executionPair.values());
338
+ return {
339
+ base: { stage: (id) => baseStage.get(id) ?? 'unpaired', execution: (id) => executionPair.has(id) ? `c${executionPair.get(id)}` : 'unpaired' },
340
+ cand: { stage: (id) => candStage.get(id) ?? 'unpaired', execution: (id) => pairedCandExecs.has(id) ? `c${id}` : 'unpaired' },
341
+ };
342
+ }
343
+
344
+ // A finding is the same finding in both runs when `findingKey` says so (type, rule and location,
345
+ // never value or band). One with a counterpart is not introduced or resolved: a different band
346
+ // makes it `changed`. Each row aggregates findings of one rule and band, with the stage names
347
+ // of the side the findings are on.
348
+ export function findingsDelta(baseSnap , candSnap , stagePairs = []) {
349
+ const locators = findingLocators(baseSnap, candSnap, stagePairs);
350
+ const diff = diffFindings(baseSnap.catalog, candSnap.catalog, locators.base, locators.cand);
351
+ const stageNames = (snap , findings ) => {
352
+ const names = new Set ();
353
+ for (const f of findings) {
354
+ const name = f.stageId != null ? snap.stages.get(f.stageId)?.name : undefined;
355
+ if (name) names.add(name);
262
356
  }
263
- return m;
357
+ return [...names].sort();
264
358
  };
265
- const b = tally(baseSnap), c = tally(candSnap);
266
- const introduced = [], resolved = [];
267
- for (const key of [...new Set([...b.keys(), ...c.keys()])].sort()) {
268
- const be = b.get(key), ce = c.get(key);
269
- const baseCount = be?.count ?? 0;
270
- const candCount = ce?.count ?? 0;
271
- if (candCount === baseCount) continue;
272
- const meta = ce ?? be ;
273
- const more = candCount > baseCount ? ce : be ; // the side with more supplies the labels
274
- const row = { rule: meta.rule, type: meta.type, impactBand: meta.impactBand, baseCount, candCount,
275
- delta: candCount - baseCount, stages: [...more.stages].sort() };
276
- (candCount > baseCount ? introduced : resolved).push(row);
277
- }
278
- return { introduced, resolved };
359
+ const stageLabelsOf = (names ) => [...new Set(names.map(stageDisplayLabel))].sort();
360
+ const baseKeyOf = findingKeyer(baseSnap), candKeyOf = findingKeyer(candSnap);
361
+ const keysOf = (findings , keyOf ) => [...new Set(findings.map(keyOf))].sort();
362
+ const group = (items , keyOf ) => {
363
+ const groups = new Map ();
364
+ for (const item of items) {
365
+ const key = keyOf(item);
366
+ const members = groups.get(key);
367
+ if (members) members.push(item); else groups.set(key, [item]);
368
+ }
369
+ return [...groups].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
370
+ };
371
+ const rows = (findings , snap , side ) =>
372
+ group(findings, (f) => `${findingRule(f)}§${findingBand(f)}`).map(([, fs]) => {
373
+ const count = fs.length;
374
+ const stages = stageNames(snap, fs);
375
+ return {
376
+ rule: findingRule(fs[0]), type: fs[0].type, impactBand: findingBand(fs[0]),
377
+ baseCount: side === 'base' ? count : 0, candCount: side === 'cand' ? count : 0,
378
+ delta: side === 'cand' ? count : -count, stages, stageLabels: stageLabelsOf(stages),
379
+ keys: keysOf(fs, side === 'cand' ? candKeyOf : baseKeyOf),
380
+ };
381
+ });
382
+ const changed = group(
383
+ diff.changed, (c) => `${findingRule(c.candidate)}§${findingBand(c.baseline)}§${findingBand(c.candidate)}`,
384
+ ).map(([, cs]) => {
385
+ const stages = stageNames(candSnap, cs.map((c) => c.candidate));
386
+ return {
387
+ rule: findingRule(cs[0].candidate), type: cs[0].candidate.type,
388
+ baseImpactBand: findingBand(cs[0].baseline), candImpactBand: findingBand(cs[0].candidate),
389
+ count: cs.length, stages, stageLabels: stageLabelsOf(stages),
390
+ keys: keysOf(cs.map((c) => c.candidate), candKeyOf), baseKeys: keysOf(cs.map((c) => c.baseline), baseKeyOf),
391
+ };
392
+ });
393
+ return { introduced: rows(diff.introduced, candSnap, 'cand'), resolved: rows(diff.resolved, baseSnap, 'base'), changed };
279
394
  }
280
395
 
281
396
  function namesConflict(a , b ) {
@@ -340,16 +455,23 @@ const EMPTY_TASK_DATA = new Map ();
340
455
 
341
456
 
342
457
 
343
-
458
+
344
459
 
345
460
 
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
346
471
 
347
472
 
348
- // Wraps the analyze()-to-compareRuns() snapshot-building sequence shared by
349
- // the CLI's --baseline path and mcp-tools.ts's compareRuns tool: both need
350
- // captureSnapshot (with an empty taskDataCache, the interactive drill-down
351
- // cache, never read by compareRuns/matchStages/metricDeltas/findingsDelta)
352
- // for each side before diffing them.
473
+ // Snapshot-building shared by the CLI's --baseline path and mcp-tools.ts's compareRuns tool. The
474
+ // empty taskDataCache is the interactive drill-down cache, which comparison never reads.
353
475
  export function buildComparison(
354
476
  baseline ,
355
477
  candidate ,
@@ -373,7 +495,11 @@ export function compareRuns(
373
495
  options = {},
374
496
  ) {
375
497
  const baseSnap = baseline.snapshot, candSnap = candidate.snapshot;
376
- // The old exact matcher stays the source of `matchedCoverage`; pairs and the gate come from the aligner.
498
+ const noiseBand = options.noiseBand ?? DEFAULT_NOISE_BAND;
499
+ if (!Number.isFinite(noiseBand) || noiseBand < 0 || noiseBand > 1) {
500
+ throw new Error(`noiseBand must be a number from 0 to 1, got ${noiseBand}.`);
501
+ }
502
+ // `matchedCoverage` comes from the exact matcher; pairs and the gate come from the aligner.
377
503
  const match = matchStages(baseSnap, candSnap);
378
504
  const alignment = alignStages(baseSnap, candSnap, { normalizePath: compileNormalizePatterns(options.normalizePath) });
379
505
  const runtimeCoverage = alignment.runtimeCoverage;
@@ -398,24 +524,29 @@ export function compareRuns(
398
524
  : lowCoverage
399
525
  ? `Only ${share} of executor run time is in matched stages, so per-stage rows mostly compare unrelated work.`
400
526
  : null;
527
+ const metrics = metricDeltas(baseSnap, candSnap, noiseBand);
401
528
  return {
402
529
  baselineLabel: baseline.label, candidateLabel: candidate.label,
530
+ baselineApp: runAppIdentity(baseSnap.app), candidateApp: runAppIdentity(candSnap.app),
403
531
  confidence,
404
532
  reason,
405
533
  matchedCoverage: match.coverage,
406
534
  comparisonSchemaVersion: COMPARISON_SCHEMA_VERSION,
407
535
  runtimeCoverage,
408
- stagePairs: alignment.pairs,
536
+ stagePairs: alignment.pairs.map((p) => ({
537
+ ...p, stageLabel: stageDisplayLabel(baseSnap.stages.get(p.baseStageIds[0])?.name ?? ''),
538
+ })),
409
539
  unmatched: alignment.unmatched,
410
540
  replanned: alignment.replanned,
411
541
  bookkeepingStageIds: alignment.bookkeepingStageIds,
412
542
  executionAlignment: alignment.executionAlignment,
413
- metrics: metricDeltas(baseSnap, candSnap),
414
- findings: findingsDelta(baseSnap, candSnap),
543
+ metrics,
544
+ findings: findingsDelta(baseSnap, candSnap, alignment.pairs),
415
545
  stageSkew: stageSkewDeltas(baseSnap, candSnap, alignment.pairs),
416
546
  baseStages: stageList(baseSnap),
417
547
  candStages: stageList(candSnap),
418
548
  jobOutcomes: { baseline: jobOutcome(baseSnap), candidate: jobOutcome(candSnap) },
549
+ comparability: buildComparability(baseSnap, candSnap, alignment, metrics, { ignoreConf: options.ignoreConf, confRedactRegex: options.confRedactRegex, noiseBand }),
419
550
  };
420
551
  }
421
552
 
@@ -429,6 +560,31 @@ function renderFindingsSection(title , findings )
429
560
  return lines;
430
561
  }
431
562
 
563
+ // Findings both runs have whose impact band moved: `### Changed findings (<count>)`, one bullet each.
564
+ function renderChangedSection(changed ) {
565
+ const lines = [`### Changed findings (${changed.length})`, ''];
566
+ for (const f of changed) {
567
+ lines.push(`- ${f.rule}: ${f.baseImpactBand} -> ${f.candImpactBand}${f.count > 1 ? ` (${f.count} findings)` : ''}`);
568
+ }
569
+ return lines;
570
+ }
571
+
572
+ // What the runs share, as `- key: value` bullets: configuration, plan, data volume and executors.
573
+ function renderComparabilitySection({ conf, plan, volume, capacity } ) {
574
+ const confLine = conf.equal == null ? 'a run records no Spark properties'
575
+ : conf.equal ? 'same' : `${conf.differences.length} differ (${conf.differences.map((d) => d.key).join(', ')})`;
576
+ const pair = (f ) => `${f.baseline ?? 'n/a'} -> ${f.candidate ?? 'n/a'}`;
577
+ const execs = (side ) =>
578
+ `${side.meanExecutors == null ? 'n/a' : side.meanExecutors.toFixed(1)} mean / ${side.peakExecutors ?? 'n/a'} peak`;
579
+ return [
580
+ '### Comparability', '',
581
+ `- configuration: ${confLine}`,
582
+ `- plan: ${plan.equal ? 'every stage pairs' : `${plan.unmatched.baseline} baseline / ${plan.unmatched.candidate} candidate stages unmatched, ${plan.replanned} re-planned`}`,
583
+ `- input bytes: ${pair(volume.inputBytes)}, output rows: ${pair(volume.outputRows)}, output bytes: ${pair(volume.outputBytes)}`,
584
+ `- executors: ${execs(capacity.baseline)} -> ${execs(capacity.candidate)}`,
585
+ ];
586
+ }
587
+
432
588
  // Small Markdown renderer for the comparison section, matching
433
589
  // evidence-report.ts's renderMarkdown house style (## section heading, ###
434
590
  // subheadings, `- key: value` bullets). Shared by the CLI's --baseline
@@ -458,6 +614,8 @@ export function renderComparisonMarkdown(
458
614
  lines.push(`- runtime coverage: ${comparison.runtimeCoverage === null ? 'n/a' : `${(comparison.runtimeCoverage * 100).toFixed(1)}%`}`);
459
615
  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`);
460
616
  lines.push('');
617
+ lines.push(...renderComparabilitySection(comparison.comparability));
618
+ lines.push('');
461
619
  lines.push('### Metric deltas');
462
620
  lines.push('');
463
621
  for (const m of comparison.metrics) {
@@ -467,5 +625,7 @@ export function renderComparisonMarkdown(
467
625
  lines.push(...renderFindingsSection('Introduced findings', comparison.findings.introduced));
468
626
  lines.push('');
469
627
  lines.push(...renderFindingsSection('Resolved findings', comparison.findings.resolved));
628
+ lines.push('');
629
+ lines.push(...renderChangedSection(comparison.findings.changed));
470
630
  return lines.join('\n');
471
631
  }
@@ -0,0 +1,12 @@
1
+
2
+
3
+ /** Which Spark application a report describes: its id and attempt id, null where the log has none
4
+ * (an attempt id is written only by a cluster-mode run). */
5
+
6
+
7
+
8
+
9
+
10
+ export function runAppIdentity(app ) {
11
+ return { id: app?.id ?? null, attemptId: typeof app?.attemptId === 'string' ? app.attemptId : null };
12
+ }
@@ -22,6 +22,7 @@ import {
22
22
  } from './run-verdict.js';
23
23
  import { recommendationText } from './finding-names.js';
24
24
  import { getScorecardEstimates } from './scorecard-estimates.js';
25
+ import { singleStageId } from './finding-filter-predicate.js';
25
26
  import { checkConcurrentJobGroups } from './job-groups.js';
26
27
  import { computeWallClock } from './wall-clock.js';
27
28
  import { computeWastedCoreHours, } from './wasted-core-hours.js';
@@ -57,6 +58,15 @@ import { computeWastedCoreHours, } from './wasted-cor
57
58
 
58
59
 
59
60
 
61
+ /** One cost-lane entry: a finding ranked by the measured busy core time its fix removes.
62
+ * `leadIndex` names the finding. */
63
+
64
+
65
+
66
+
67
+
68
+
69
+
60
70
 
61
71
 
62
72
 
@@ -64,7 +74,12 @@ import { computeWastedCoreHours, } from './wasted-cor
64
74
 
65
75
 
66
76
 
77
+
67
78
 
79
+
80
+
81
+
82
+
68
83
 
69
84
 
70
85
 
@@ -265,6 +280,12 @@ export function interpretRun(appModel , catalog , configFindi
265
280
  clean: verdict.facts.clean,
266
281
  failureReason: outcome.reason,
267
282
  steps,
283
+ costSteps: verdict.costLane.map(({ finding, figure }) => ({
284
+ leadIndex: indexOf.get(finding) ,
285
+ stageId: singleStageId(finding),
286
+ figure,
287
+ })),
288
+ idleLine: verdict.idle?.text ?? null,
268
289
  remaining: verdict.remaining,
269
290
  copyText: verdict.copyText,
270
291
  },
@@ -3,7 +3,7 @@
3
3
  // moves independently of the evidence report's. A figure the log cannot provide is null, never 0.
4
4
  import { computeAllocation, } from './allocation.js';
5
5
  import { computeSkewRatio, ENTRY_BY_TYPE, } from './detectors.js';
6
- import { totalExecutorCpuMs, withEarlierAttempts } from './run-totals.js';
6
+ import { totalExecutorCpuMs, totalOutputRows, withEarlierAttempts } from './run-totals.js';
7
7
  import { isPythonStage } from './python-stage.js';
8
8
  import { stageIdentity } from './run-comparison.js';
9
9
  import { hasCompleteApplicationInterval } from './scorecard-estimates.js';
@@ -128,7 +128,7 @@ function stageMetrics(stages , minTasksForP95 ) {
128
128
  shuffleWriteBytes: fromTasks((s) => s.shuffleWriteBytes),
129
129
  inputBytes: fromTasks((s) => s.inputBytes),
130
130
  outputBytes: fromTasks((s) => s.outputBytes),
131
- outputRows: sumOf(finished, (s) => s.outputRecords),
131
+ outputRows: totalOutputRows(attempts),
132
132
  peakExecutionMemoryBytes: peakExecutionMemory(finished),
133
133
  taskCount: tasks != null && tasks > 0 ? tasks : null,
134
134
  failedTasks: fromTasks((s) => s.failedTasks),
@@ -1,7 +1,8 @@
1
- import { runFindings } from './evidence-report.js';
1
+ import { cacheFor, runFindings, } from './evidence-report.js';
2
2
  import { redactRunModel } from './redact.js';
3
3
  import { computeRunMetrics, } from './run-metrics.js';
4
4
  import { buildEffectiveConf, } from './effective-conf.js';
5
+ import { runAppIdentity, } from './run-identity.js';
5
6
 
6
7
 
7
8
 
@@ -13,25 +14,46 @@ import { buildEffectiveConf, } from './effective-conf.js';
13
14
 
14
15
 
15
16
 
17
+
18
+
19
+ // One entry per run model and threshold overrides, kept apart for the plain and the redacted model.
20
+ // A WeakMap needs no invalidation: the entry is collectible once the model is (see runFindings).
21
+ const plainBlocksCache = new WeakMap();
22
+ const redactedBlocksCache = new WeakMap();
23
+
24
+ // The app the blocks read (redacted under `redact`) and the run's `metrics`, the costly part,
25
+ // computed once per run and options.
26
+ function runBlocksBase(appModel , redact , thresholds ) {
27
+ const cache = cacheFor(redact ? redactedBlocksCache : plainBlocksCache, thresholds);
28
+ const cached = cache.get(appModel);
29
+ if (cached) return cached;
30
+ let blocksModel = appModel;
31
+ if (redact) {
32
+ const { catalog, config } = runFindings(appModel, thresholds);
33
+ blocksModel = redactRunModel(appModel, catalog, config).appModel;
34
+ }
35
+ const base = { app: blocksModel.app, metrics: computeRunMetrics(blocksModel, thresholds) };
36
+ cache.set(appModel, base);
37
+ return base;
38
+ }
39
+
16
40
  /** The CLI report's `metrics` and `effectiveConf` blocks, shared with MCP diagnose_run. Under
17
41
  * `redact` they come from the run redacted with the report's own inputs, so host and app
18
42
  * pseudonyms match the report's. `confKeys`/`confRedactRegex` narrow and mask `effectiveConf`.
19
- * `generator`, when the surface supplies it, is returned as given: it holds no run identifiers. */
43
+ * `generator`, when the surface supplies it, is returned as given: it holds no run identifiers.
44
+ * `app` names the Spark application (redacted under `redact`), so two reports can be told apart. */
20
45
  export function runOutputBlocks(
21
46
  appModel ,
22
47
  { redact, thresholds, confKeys, confRedactRegex, generator }
23
48
 
24
49
 
25
50
  = {},
26
- ) {
27
- let blocksModel = appModel;
28
- if (redact) {
29
- const { catalog, config } = runFindings(appModel, thresholds);
30
- blocksModel = redactRunModel(appModel, catalog, config).appModel;
31
- }
51
+ ) {
52
+ const { app, metrics } = runBlocksBase(appModel, redact, thresholds);
32
53
  return {
33
54
  ...(generator ? { generator } : {}),
34
- metrics: computeRunMetrics(blocksModel, thresholds),
35
- effectiveConf: buildEffectiveConf(blocksModel.app, { keys: confKeys, userPattern: confRedactRegex }),
55
+ app: runAppIdentity(app),
56
+ metrics,
57
+ effectiveConf: buildEffectiveConf(app, { keys: confKeys, userPattern: confRedactRegex }),
36
58
  };
37
59
  }
@@ -22,3 +22,15 @@ export function totalExecutorCpuMs(stages
22
22
  for (const s of stages) if (typeof s.executorCpuTime === 'number' && Number.isFinite(s.executorCpuTime) && s.executorCpuTime > 0) ns += s.executorCpuTime;
23
23
  return ns > 0 ? nsToMs(ns) : null;
24
24
  }
25
+
26
+ /** Summed output rows over the given stage attempts (see `withEarlierAttempts`). Like the other
27
+ * task-derived totals it is null until some attempt finished a task, and null when no stage
28
+ * recorded a row count, never 0. */
29
+ export function totalOutputRows(attempts ) {
30
+ if (!attempts.some((s) => (s.taskCount ?? 0) > 0)) return null;
31
+ let sum = 0, present = false;
32
+ for (const s of attempts) {
33
+ if (typeof s.outputRecords === 'number' && Number.isFinite(s.outputRecords)) { sum += s.outputRecords; present = true; }
34
+ }
35
+ return present ? sum : null;
36
+ }