sparkforensics-server 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/package.json +3 -3
  2. package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-5hDGIXi4.js} +1 -1
  3. package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CT2SCnJJ.js} +1 -1
  4. package/public/assets/{CachingOpportunity-Cfw3wAFa.js → CachingOpportunity-D_O_WkbH.js} +1 -1
  5. package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-B38OiAOp.js} +1 -1
  6. package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-BoplryCH.js} +1 -1
  7. package/public/assets/{CoreUsageArea-BObEeenE.js → CoreUsageArea-csgwNxkl.js} +1 -1
  8. package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-BgKodJl2.js} +2 -2
  9. package/public/assets/{DisclosureContext-Ds0hcZFn.js → DisclosureContext-B0siMVdg.js} +1 -1
  10. package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-BqS2A2j4.js} +1 -1
  11. package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-Dp52QGWo.js} +1 -1
  12. package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-CT1yzR_s.js} +1 -1
  13. package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-C7Qkjtjn.js} +1 -1
  14. package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-WHl3ELfh.js} +1 -1
  15. package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-D8IUDHJI.js} +1 -1
  16. package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-6oSSAQ_R.js} +1 -1
  17. package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
  18. package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-BwdFL-mw.js} +1 -1
  19. package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DcxmiQWL.js} +1 -1
  20. package/public/assets/{PlanGraphRoute-NV5mCXRm.js → PlanGraphRoute-pqnHTt4i.js} +1 -1
  21. package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-BTbeHkJc.js} +1 -1
  22. package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-CRhrYNE0.js} +1 -1
  23. package/public/assets/{Skew-PL79TppX.js → Skew-DSNXRG9V.js} +1 -1
  24. package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-B7Lwu2EU.js} +1 -1
  25. package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-C082c2rd.js} +1 -1
  26. package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-BS2vpadr.js} +1 -1
  27. package/public/assets/{Spill-CH1bNpxL.js → Spill-B4DVKHXx.js} +1 -1
  28. package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-D3ac9Sou.js} +1 -1
  29. package/public/assets/{StageFindingGroup-B1A0nf-e.js → StageFindingGroup-D9naK109.js} +1 -1
  30. package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-pI1nTwVf.js} +1 -1
  31. package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-eO5le8iG.js} +1 -1
  32. package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BaNyfNvL.js} +1 -1
  33. package/public/assets/{Straggler-DHLTiBY-.js → Straggler-D5Fnok_y.js} +1 -1
  34. package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-BWnC-K_G.js} +1 -1
  35. package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-8_5lpq_2.js} +1 -1
  36. package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DJ0XSwth.js} +1 -1
  37. package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cys8Z6Yn.js} +1 -1
  38. package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-BTQndbof.js} +1 -1
  39. package/public/assets/index-DCeWge12.js +13 -0
  40. package/public/assets/parser-worker-DmeCnLWU.js +122 -0
  41. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  42. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  43. package/public/assets/{select-DkjFHlXR.js → select-DIYN6D3y.js} +1 -1
  44. package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-BxvdFnPk.js} +1 -1
  45. package/public/assets/{useRecentFiles-Bb4-ukVz.js → useRecentFiles-C3Ov47U7.js} +4 -4
  46. package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-B8SRfAoG.js} +1 -1
  47. package/public/docs/404.html +2 -2
  48. package/public/docs/assets/{app.yjczoibf.js → app.D2GqAkhy.js} +1 -1
  49. package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
  50. package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
  51. package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.BuxVpb0H.js} +2 -2
  52. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  53. 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
  54. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  77. package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
  78. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  79. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  80. 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
  81. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  82. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  107. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  108. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  109. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  110. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  111. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  112. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  113. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  114. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  115. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +22 -0
  116. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +1 -0
  117. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  118. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  119. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  120. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  121. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  122. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  123. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  124. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  125. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.js +55 -0
  126. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js +1 -0
  127. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  128. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  129. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js +26 -0
  130. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js +1 -0
  131. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  132. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  133. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  134. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  135. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  136. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  137. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  138. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  139. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  140. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  141. package/public/docs/assets/{user-guide_run-comparison.md.qin0a4IX.js → user-guide_run-comparison.md.B6aNtDYF.js} +1 -1
  142. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
  143. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
  144. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
  145. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +1 -0
  146. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  147. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  148. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  149. package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
  150. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  151. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  152. package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
  153. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  154. package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
  155. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  156. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  157. package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
  158. package/public/docs/contributor-guide/architecture/index.html +5 -5
  159. package/public/docs/contributor-guide/architecture/overview.html +5 -5
  160. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  161. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  162. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  163. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  164. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  165. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  166. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  167. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  168. package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
  169. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  170. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  171. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  172. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  173. package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
  174. package/public/docs/contributor-guide/contributing.html +4 -4
  175. package/public/docs/contributor-guide/development-setup.html +4 -4
  176. package/public/docs/contributor-guide/testing.html +4 -4
  177. package/public/docs/hashmap.json +1 -1
  178. package/public/docs/index.html +3 -3
  179. package/public/docs/tuning-reference/anti-patterns.html +3 -3
  180. package/public/docs/tuning-reference/aqe.html +3 -3
  181. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
  182. package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
  183. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
  184. package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
  185. package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
  186. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
  187. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
  188. package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
  189. package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
  190. package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
  191. package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
  192. package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
  193. package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
  194. package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
  195. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
  196. package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
  197. package/public/docs/tuning-reference/caching.html +3 -3
  198. package/public/docs/tuning-reference/cluster-config.html +3 -3
  199. package/public/docs/tuning-reference/config.html +3 -3
  200. package/public/docs/tuning-reference/data-formats.html +3 -3
  201. package/public/docs/tuning-reference/index.html +3 -3
  202. package/public/docs/tuning-reference/intro.html +3 -3
  203. package/public/docs/tuning-reference/joins.html +3 -3
  204. package/public/docs/tuning-reference/memory-model.html +3 -3
  205. package/public/docs/tuning-reference/metrics.html +3 -3
  206. package/public/docs/tuning-reference/partitioning.html +3 -3
  207. package/public/docs/tuning-reference/pyspark.html +3 -3
  208. package/public/docs/tuning-reference/shuffle.html +3 -3
  209. package/public/docs/tuning-reference/spark-architecture.html +3 -3
  210. package/public/docs/tuning-reference/table-formats.html +3 -3
  211. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  212. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  213. package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
  214. package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -0
  215. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  216. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  217. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  218. package/public/docs/user-guide/getting-started.html +5 -28
  219. package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -0
  220. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  221. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  222. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  223. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  224. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  225. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  226. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  227. package/public/docs/user-guide/mcp-tools.html +8 -140
  228. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  229. package/public/docs/user-guide/run-comparison.html +5 -5
  230. package/public/docs/user-guide/understanding-findings.html +5 -5
  231. package/public/export-template.html +44 -44
  232. package/public/index.html +4 -4
  233. package/vendor-core/analyzer.js +1 -1
  234. package/vendor-core/api-merge-writes.js +80 -0
  235. package/vendor-core/comparison-output.js +49 -0
  236. package/vendor-core/core-source-hash.txt +1 -1
  237. package/vendor-core/delta-commands.js +13 -0
  238. package/vendor-core/delta-log-paths.js +42 -0
  239. package/vendor-core/delta-targets.js +127 -0
  240. package/vendor-core/event-handlers.js +95 -9
  241. package/vendor-core/event-schemas.js +3 -1
  242. package/vendor-core/evidence-report.js +13 -4
  243. package/vendor-core/mcp-server-factory.js +13 -6
  244. package/vendor-core/mcp-tools.js +44 -35
  245. package/vendor-core/plan-graph-model.js +1 -1
  246. package/vendor-core/plan-summary.js +13 -10
  247. package/vendor-core/run-comparison.js +79 -120
  248. package/vendor-core/run-output.js +26 -0
  249. package/vendor-core/stage-alignment.js +262 -0
  250. package/vendor-core/stage-identity.js +112 -0
  251. package/vendor-core/types.js +5 -0
  252. package/vendor-core/write-target-args.js +48 -0
  253. package/vendor-core/write-targets.js +35 -52
  254. package/public/assets/index-DaC8A2FS.js +0 -13
  255. package/public/assets/parser-worker-CfDvPkwF.js +0 -122
  256. package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
  257. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
  258. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
  259. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
  260. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
  261. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
  262. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
  263. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
  264. package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
  265. package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
  266. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
  267. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
  268. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
  269. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
  270. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
  271. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
  272. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
  273. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
  274. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
  275. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
  276. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
  277. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
  278. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
  279. /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
  280. /package/public/docs/assets/{user-guide_understanding-findings.md.DzReyCXb.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
package/public/index.html CHANGED
@@ -23,12 +23,12 @@
23
23
  document.documentElement.setAttribute('data-theme', 'light');
24
24
  } catch (_) {}
25
25
  </script>
26
- <script type="module" crossorigin src="./assets/index-DaC8A2FS.js"></script>
26
+ <script type="module" crossorigin src="./assets/index-DCeWge12.js"></script>
27
27
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-hePW80VL.js">
28
28
  <link rel="modulepreload" crossorigin href="./assets/charts-vendor-0VMaLVLN.js">
29
- <link rel="modulepreload" crossorigin href="./assets/plan-graph-vendor-B0sEms9Z.js">
30
- <link rel="modulepreload" crossorigin href="./assets/react-vendor-BAKt3vny.js">
31
- <link rel="modulepreload" crossorigin href="./assets/DisclosureContext-Ds0hcZFn.js">
29
+ <link rel="modulepreload" crossorigin href="./assets/plan-graph-vendor-BYl-AzN5.js">
30
+ <link rel="modulepreload" crossorigin href="./assets/react-vendor-DEdfrRTJ.js">
31
+ <link rel="modulepreload" crossorigin href="./assets/DisclosureContext-B0siMVdg.js">
32
32
  <link rel="stylesheet" crossorigin href="./assets/plan-graph-vendor-DLioOiRN.css">
33
33
  <link rel="stylesheet" crossorigin href="./assets/index-BcqTLKFp.css">
34
34
  </head>
@@ -90,7 +90,7 @@ export function findingId(f ) {
90
90
 
91
91
  // skew (either branch) and straggler both claim the stage's replayed tail recovery
92
92
  // (tailReplayRecoveryMs via tailRecoveryMs): the same slow-task tail reported by two detectors
93
- // (see "Overlap caveat: skew / straggler" in impact-estimation.md). skew's branch only changes
93
+ // (see "Overlap caveat: skew / straggler" in impact-estimation/caveats-tuning-and-coverage.md). skew's branch only changes
94
94
  // the fallback single-task delta on a stage without the replay, so every skew + straggler pair
95
95
  // on a stage is flagged. Flags both sides via validationRequired (rather than suppressing
96
96
  // either) so neither finding's own diagnostic value is lost; the flag rides the same
@@ -0,0 +1,80 @@
1
+ // DeltaTable API merges run no command node: each internal query is its own root execution, tied
2
+ // together only by a "MERGE operation" description and adjacent ids. One DeltaMerge write is
3
+ // reported per run of such executions, with a path only when no other merge can be mixed into it.
4
+ import { soleLogPath } from './delta-log-paths.js';
5
+
6
+
7
+
8
+
9
+
10
+ const API_MERGE_COMMAND = 'DeltaMerge';
11
+
12
+ // Delta labels its MERGE sub-queries "MERGE operation - <phase>", prefixed "Delta: " in some.
13
+ const MERGE_DESCRIPTION = /^(?:Delta: )?MERGE operation\b/;
14
+
15
+ // The sub-queries that write: "writing new files ...", "Rewriting N files". A group without one
16
+ // only scanned, and a scan may read the merge's source rather than its target.
17
+ const MERGE_WRITE_PHASE = /\b(?:re)?writing\b/i;
18
+
19
+
20
+
21
+ // Selected executions have a MERGE description. A SQL MERGE's own sub-queries carry the same
22
+ // description but belong to the MergeIntoCommand execution they share a root with, which is
23
+ // attributed through its own node (resolveDeltaCommandTarget), so they are left out. A sub-query
24
+ // of an API merge may have another of its executions as its root, so a root alone does not exclude.
25
+ function selectMergeExecutions(sql ) {
26
+ const isSqlMergeRoot = (id ) =>
27
+ id !== undefined && /^Execute\s+MergeIntoCommand\b/.test(sql.get(id)?.planTree?.name ?? '');
28
+ return [...sql.values()]
29
+ .filter((exec) => MERGE_DESCRIPTION.test(exec.description ?? '') && !isSqlMergeRoot(exec.rootExecutionId))
30
+ .sort((a, b) => a.id - b.id);
31
+ }
32
+
33
+ // A run of consecutive execution ids. Nothing in the log ties the members of one merge together,
34
+ // so adjacency is a guess, and a wrong one fuses or splits merges. Each way it can go wrong is
35
+ // marked `unclean` instead of attributed:
36
+ // - the ids of two merges that ran concurrently interleave, so their start-time spans overlap;
37
+ // - a start time is missing, so the span is unknown.
38
+ // - no member writes, so the one path it names may be a source's.
39
+ // Merges that ran back to back with no other execution between them still fuse into one run; the
40
+ // run then names two paths and gets no target.
41
+ function groupMerges(selected ) {
42
+ const groups = [];
43
+ for (const exec of selected) {
44
+ const last = groups[groups.length - 1];
45
+ if (last && last.members[last.members.length - 1].id + 1 === exec.id) last.members.push(exec);
46
+ else groups.push({ members: [exec], unclean: null });
47
+ }
48
+ const spans = groups.map(({ members }) => {
49
+ const starts = members.map((m) => m.startTime);
50
+ return starts.every((t) => typeof t === 'number') ? [Math.min(...starts), Math.max(...starts)] : null;
51
+ });
52
+ groups.forEach((group, i) => {
53
+ const mine = spans[i];
54
+ if (mine === null) { group.unclean = 'start time missing'; return; }
55
+ const overlaps = spans.some((other, j) => j !== i && other !== null && other[0] <= mine[1] && mine[0] <= other[1]);
56
+ if (overlaps) group.unclean = 'overlaps another merge in time';
57
+ else if (!group.members.some((m) => MERGE_WRITE_PHASE.test(m.description ?? ''))) group.unclean = 'no write phase';
58
+ });
59
+ return groups;
60
+ }
61
+
62
+ /** One DeltaMerge write per run of API merge executions; null target unless one path is certain. */
63
+ export function apiMergeWrites(sql ) {
64
+ return groupMerges(selectMergeExecutions(sql)).map((group) => {
65
+ const first = group.members[0].id;
66
+ const last = group.members[group.members.length - 1].id;
67
+ const resolved = group.unclean === null ? soleLogPath(group.members) : null;
68
+ const span = first === last ? `${first}` : `${first}-${last}`;
69
+ return {
70
+ sqlExecutionId: first,
71
+ nodeId: null,
72
+ command: API_MERGE_COMMAND,
73
+ recognized: true,
74
+ kind: resolved?.kind ?? null,
75
+ target: resolved?.target ?? null,
76
+ outputRows: null,
77
+ raw: `Delta MERGE operation, executions ${span}${group.unclean ? ` (${group.unclean})` : ''}`,
78
+ };
79
+ });
80
+ }
@@ -0,0 +1,49 @@
1
+ import { buildComparison, } from './run-comparison.js';
2
+ import { comparisonVerdict, } from './comparison-verdict.js';
3
+ import { redactComparison } from './redact.js';
4
+
5
+
6
+
7
+
8
+ // The comparison a surface reports: the CLI's `comparison` object and the shared part of MCP
9
+ // compare_runs. A field added here reaches every surface.
10
+ // `stagePairs` is the one large field (a row per paired stage), so the `summary` view leaves it out.
11
+
12
+
13
+
14
+
15
+
16
+
17
+ /** `full` carries every field (the CLI); `summary` leaves out `stagePairs` (MCP, which returns them
18
+ * only when asked). */
19
+
20
+
21
+ /** The reported projection of a comparison. Key order is the CLI's JSON order. */
22
+ export function comparisonOutput(comparison , view = 'full') {
23
+ return {
24
+ verdict: comparisonVerdict(comparison),
25
+ confidence: comparison.confidence,
26
+ reason: comparison.reason,
27
+ matchedCoverage: comparison.matchedCoverage,
28
+ runtimeCoverage: comparison.runtimeCoverage,
29
+ metrics: comparison.metrics,
30
+ findings: comparison.findings,
31
+ comparisonSchemaVersion: comparison.comparisonSchemaVersion,
32
+ ...(view === 'full' ? { stagePairs: comparison.stagePairs } : {}),
33
+ unmatched: comparison.unmatched,
34
+ replanned: comparison.replanned,
35
+ bookkeepingStageIds: comparison.bookkeepingStageIds,
36
+ };
37
+ }
38
+
39
+ /** Diffs the two runs and applies `redact` here, so no surface can skip it (stage names carry raw
40
+ * Spark text that can embed a host or IP). `comparison` is the (redacted) raw result for budgets
41
+ * and Markdown; `output` is the projection to serialize. */
42
+ export function buildComparisonOutput(
43
+ baseline , candidate ,
44
+ { redact, normalizePath, view } = {},
45
+ ) {
46
+ const built = buildComparison(baseline, candidate, { normalizePath });
47
+ const comparison = redact ? redactComparison(built) : built;
48
+ return { comparison, output: comparisonOutput(comparison, view) };
49
+ }
@@ -1 +1 @@
1
- ab36c5fdcdc1a32a4e6206459e843e8d2267a90e6f1de29671827315d81bc374
1
+ 6e0ac29f74ae092b14c5700dfa86d3273fce7d1af59005c6e2e450a91da92d5e
@@ -0,0 +1,13 @@
1
+ // Delta write commands whose root node is a bare name in sparkPlanInfo on real logs. The parser keeps
2
+ // their `Arguments:` line (event-handlers.ts) and delta-targets.ts reads their target from it; the
3
+ // handler table there is typed by this list, so a command added here must get a handler.
4
+ export const DELTA_COMMANDS = [
5
+ 'MergeIntoCommand', 'UpdateCommand', 'DeleteCommand', 'WriteIntoDelta', 'WriteIntoDeltaCommand',
6
+ 'SaveIntoDataSourceCommand',
7
+ ] ;
8
+
9
+
10
+
11
+ export function isDeltaCommand(name ) {
12
+ return (DELTA_COMMANDS ).includes(name);
13
+ }
@@ -0,0 +1,42 @@
1
+ // The `<table path>/_delta_log` paths that plans name: the only trace of which Delta table a command
2
+ // or API merge touched when its plan carries no target. A scan may read a source too, so callers
3
+ // take a path only when exactly one is named.
4
+ import { walkPlanTree } from './plan-tree-walk.js';
5
+ import { isCut, } from './write-target-args.js';
6
+
7
+
8
+ // A URI or absolute path before `/_delta_log`, as printed in `... State #0 - <path>/_delta_log[cols]`
9
+ // scans and `DeltaLogFileIndex [<path>/_delta_log/<file>, ...]`.
10
+ const DELTA_LOG_PATH = /((?:[A-Za-z][A-Za-z0-9+.-]*:\/{1,3}|\/)[^\s,[\]()#]+?)\/_delta_log(?=[/[\],\s)]|$)/g;
11
+
12
+
13
+
14
+ // Every distinct table path named in the plans; `unreliable` when a plan is missing or a path cut.
15
+ function deltaLogPaths(executions ) {
16
+ const paths = new Set ();
17
+ let unreliable = false;
18
+ for (const exec of executions) {
19
+ if (!exec.planTree) { unreliable = true; continue; }
20
+ walkPlanTree(exec.planTree, (node) => {
21
+ for (const text of [node.name, node.detail ?? '']) {
22
+ const mentions = text.split('/_delta_log').length - 1;
23
+ if (mentions === 0) continue;
24
+ const matches = [...text.matchAll(DELTA_LOG_PATH)];
25
+ // A `/_delta_log` that is not read as a path is a table this set cannot name.
26
+ if (matches.length < mentions) unreliable = true;
27
+ for (const m of matches) {
28
+ if (isCut(m[1])) unreliable = true;
29
+ else paths.add(m[1].replace(/\/+$/, ''));
30
+ }
31
+ }
32
+ }, { dedupe: true });
33
+ }
34
+ return { paths, unreliable };
35
+ }
36
+
37
+ /** The one table path the plans of `executions` name, or null for none, several or an unreadable plan. */
38
+ export function soleLogPath(executions ) {
39
+ const { paths, unreliable } = deltaLogPaths(executions);
40
+ const [path] = paths;
41
+ return !unreliable && paths.size === 1 ? { kind: 'path', target: path } : null;
42
+ }
@@ -0,0 +1,127 @@
1
+ // Delta write targets that the plan node's simpleString does not carry. A Delta command node is a
2
+ // bare name in sparkPlanInfo on real logs; its target is read from the `Arguments:` line the parser
3
+ // keeps (SqlExecution.commandArguments, one reader per command in COMMANDS), and for a command that
4
+ // always scans its target (MERGE, UPDATE, DELETE) from the single `_delta_log` path of the executions
5
+ // sharing its root. A write command may read another Delta table as its source, so it never takes
6
+ // that step. Otherwise the target is null: a rule that cannot name exactly one target names none.
7
+ import { isDeltaCommand, } from './delta-commands.js';
8
+ import { soleLogPath } from './delta-log-paths.js';
9
+ import { isCut, splitArgs, tableName, } from './write-target-args.js';
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+ /** Executions grouped by rootExecutionId, each group excluding the root itself. */
18
+ export function indexByRoot(sql ) {
19
+ const byRoot = new Map ();
20
+ for (const exec of sql.values()) {
21
+ if (exec.rootExecutionId === undefined || exec.rootExecutionId === exec.id) continue;
22
+ const list = byRoot.get(exec.rootExecutionId);
23
+ if (list) list.push(exec);
24
+ else byRoot.set(exec.rootExecutionId, [exec]);
25
+ }
26
+ return byRoot;
27
+ }
28
+
29
+ // The text after `<Command>\nArguments: `, or null when the execution kept none for this command.
30
+ function argumentsOf(exec , command ) {
31
+ const kept = exec.commandArguments;
32
+ const head = `${command}\nArguments: `;
33
+ return kept !== undefined && kept.startsWith(head) ? kept.slice(head.length) : null;
34
+ }
35
+
36
+ // A catalog table is printed as its quoted identifier, `catalog`.`db`.`t`, as one top-level
37
+ // argument. Two or more such arguments (or none) leave the table unnamed. The session catalog is
38
+ // implied by a Hive or V1 table name, so it is dropped: `spark_catalog`.`db`.`t` is db.t.
39
+ const QUOTED_TABLE = /^`[^`]+`(?:\.`[^`]+`){1,2}$/;
40
+
41
+ function tableFromArguments(args ) {
42
+ const candidates = splitArgs(args).filter((arg) => QUOTED_TABLE.test(arg.text));
43
+ if (candidates.length !== 1 || !candidates[0].terminated) return null;
44
+ const name = tableName(candidates[0].text);
45
+ if (!name || isCut(name)) return null;
46
+ const parts = name.split('.');
47
+ return { kind: 'table', target: parts.length === 3 && parts[0] === 'spark_catalog' ? parts.slice(1).join('.') : name };
48
+ }
49
+
50
+ // SaveIntoDataSourceCommand prints `<provider>@<hash>, [k=v, k2=v2], <mode>`. The target is the
51
+ // one `path` option of a Delta provider.
52
+ function pathFromSaveOptions(args ) {
53
+ const options = splitArgs(args)[1];
54
+ if (!options?.terminated || !/^\[[\s\S]*\]$/.test(options.text)) return null;
55
+ const body = options.text.slice(1, -1);
56
+ const keys = [...body.matchAll(/(?:^|, )([A-Za-z0-9_.-]+)=/g)];
57
+ const paths = keys.flatMap((key, i) => {
58
+ if (key[1].toLowerCase() !== 'path') return [];
59
+ return [body.slice(key.index + key[0].length, i + 1 < keys.length ? keys[i + 1].index : body.length)];
60
+ });
61
+ if (paths.length !== 1 || isCut(paths[0]) || /\*{5,}\(redacted\)/.test(paths[0])) return null;
62
+ return { kind: 'path', target: paths[0] };
63
+ }
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+ const tableCommand = { fromArguments: tableFromArguments, scansTarget: false };
73
+ const scanningTableCommand = { ...tableCommand, scansTarget: true };
74
+
75
+ // One entry per Delta command: a new command is added here (and to DELTA_COMMANDS), not in the resolver.
76
+ const COMMANDS = {
77
+ MergeIntoCommand: scanningTableCommand,
78
+ UpdateCommand: scanningTableCommand,
79
+ DeleteCommand: scanningTableCommand,
80
+ WriteIntoDelta: tableCommand,
81
+ WriteIntoDeltaCommand: tableCommand,
82
+ // Only the arguments say a save is a Delta one; a JDBC save with a Delta table in its source
83
+ // would otherwise take that table as its target.
84
+ SaveIntoDataSourceCommand: {
85
+ fromArguments: (args) => (args.includes('DeltaDataSource') ? pathFromSaveOptions(args) : null),
86
+ scansTarget: false,
87
+ },
88
+ };
89
+
90
+ /** Target of a Delta command whose simpleString names none; `node` must be the root of `exec`'s plan. */
91
+ export function resolveDeltaCommandTarget(
92
+ command , exec , node , byRoot ,
93
+ ) {
94
+ if (!isDeltaCommand(command) || node !== exec.planTree) return null;
95
+ const handler = COMMANDS[command];
96
+ const args = argumentsOf(exec, command);
97
+ const found = args === null ? null : handler.fromArguments(args);
98
+ if (found || !handler.scansTarget) return found;
99
+ // Child executions are the root's own only when it is the root: a command run under another
100
+ // execution shares that execution's children, which may belong to unrelated work.
101
+ return exec.rootExecutionId === exec.id ? soleLogPath(byRoot.get(exec.id) ?? []) : null;
102
+ }
103
+
104
+ // An append or overwrite of an existing Delta table (saveAsTable, INSERT INTO or OVERWRITE) is an
105
+ // `AppendDataExecV1` or `OverwriteByExpressionExecV1` whose first argument is the table object:
106
+ // `DeltaTableV2(<session>,<path>,Some(CatalogTable(\nCatalog: c\nDatabase: d\nTable: t\n...)),...)`.
107
+ // Its CatalogTable block names the table; a path-based table has `None` there and only the path.
108
+ const DELTA_TABLE_V2 = /^\w+ DeltaTableV2\([^,\s]*,([^,\n]*),(?:Some\(CatalogTable\(\n(?:Catalog: (\S+)\n)?Database: (\S+)\nTable: (\S+)\n|None,)/;
109
+ const SIMPLE_NAME = /^[A-Za-z0-9_$]+$/;
110
+
111
+ /** Target of a V1-fallback write on a DeltaTableV2 table, or null when the detail has another shape. */
112
+ export function parseDeltaTableV2(detail ) {
113
+ const m = DELTA_TABLE_V2.exec(detail);
114
+ if (!m) return null;
115
+ const [, path, catalog, database, table] = m;
116
+ if (table === undefined) return path && !isCut(path) ? { kind: 'path', target: path } : null;
117
+ if (![database, table, catalog ?? 'spark_catalog'].every((part) => SIMPLE_NAME.test(part))) return null;
118
+ // The session catalog is implied by a V1 name, so it is dropped (as for a Delta command).
119
+ const name = `${database}.${table}`;
120
+ return { kind: 'table', target: catalog === undefined || catalog === 'spark_catalog' ? name : `${catalog}.${name}` };
121
+ }
122
+
123
+ /** The append a Delta CTAS or RTAS runs under its own root: its staged table has no name. */
124
+ export function isStagedDeltaWrite(write ) {
125
+ return (write.command === 'AppendDataExecV1' || write.command === 'OverwriteByExpressionExecV1')
126
+ && /^\w+ \S*DeltaCatalog\$StagedDeltaTableV2@/.test(write.raw);
127
+ }
@@ -18,6 +18,7 @@ import {
18
18
 
19
19
 
20
20
  } from './event-schemas.js';
21
+ import { DELTA_COMMANDS } from './delta-commands.js';
21
22
  import { assertNever } from './assert-never.js';
22
23
  import { finalizeStage } from './stage-quantiles.js';
23
24
  import { MAX_FAILURE_DETAILS_PER_STAGE, extractTaskFailureDetail, taskFailureKey, } from './task-failure.js';
@@ -198,6 +199,9 @@ const MAX_TASK_SAMPLES = 20;
198
199
 
199
200
 
200
201
 
202
+
203
+
204
+
201
205
 
202
206
 
203
207
 
@@ -279,6 +283,10 @@ export function buildChunkDecoder() {
279
283
  // line again after every slice scanned about 10 GB on a 100 MB line.
280
284
  let pendingIsSqlEvent = false;
281
285
  let keyTail = '';
286
+ // Where the pending line's plan-description value starts, once its key is found but too little
287
+ // of the value has arrived to tell whether it is a small Delta command description (see
288
+ // descriptionDisposition); -1 otherwise.
289
+ let undecidedValueStart = -1;
282
290
  let skippingPlanDescription = false;
283
291
  // Length of the backslash run the skipped bytes ended with, which escapes a quote at the start
284
292
  // of the next chunk when odd.
@@ -312,11 +320,24 @@ export function buildChunkDecoder() {
312
320
  keyTail = '';
313
321
  appended = pending; // nothing of it was searched while it was shorter than the prefix
314
322
  }
315
- const keyAt = findPlanKey(appended);
316
- if (keyAt === -1) return; // the key may still arrive in a later chunk
323
+ let valueStart = undecidedValueStart;
324
+ if (valueStart === -1) {
325
+ const keyAt = findPlanKey(appended);
326
+ if (keyAt === -1) return; // the key may still arrive in a later chunk
327
+ valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
328
+ }
329
+ if (closingQuoteIndex(pending, valueStart) !== -1) { // complete: stripPlanDescription handles it
330
+ undecidedValueStart = -1;
331
+ pendingSettled = true;
332
+ return;
333
+ }
334
+ // A small kept description is not skipped: the line completes in a later chunk and
335
+ // stripPlanDescription keeps its arguments line.
336
+ const disposition = descriptionDisposition(pending, valueStart);
337
+ if (disposition === 'wait') { undecidedValueStart = valueStart; return; }
338
+ undecidedValueStart = -1;
317
339
  pendingSettled = true;
318
- const valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
319
- if (closingQuoteIndex(pending, valueStart) !== -1) return; // complete: stripPlanDescription empties it
340
+ if (disposition === 'keep') return;
320
341
  // Only a chunk whose last byte is a backslash carries a run over; any other last byte (such
321
342
  // as part of a split multibyte char) ends it.
322
343
  let run = 0;
@@ -378,6 +399,7 @@ export function buildChunkDecoder() {
378
399
  pending = pendingHead = appended = start < text.length ? text.substring(start) : '';
379
400
  pendingSettled = false;
380
401
  pendingIsSqlEvent = false;
402
+ undecidedValueStart = -1;
381
403
  }
382
404
  if (!pendingSettled && pending !== '') settlePending(buffer[buffer.length - 1], appended);
383
405
  }
@@ -1109,6 +1131,10 @@ export function startSqlExecution(event
1109
1131
  sparkPlanInfo,
1110
1132
  hadAdaptiveUpdate: false,
1111
1133
  };
1134
+ if (event.rootExecutionId !== undefined) exec.rootExecutionId = event.rootExecutionId;
1135
+ if (event.physicalPlanDescription && KEPT_ARGUMENTS.test(event.physicalPlanDescription)) {
1136
+ exec.commandArguments = event.physicalPlanDescription;
1137
+ }
1112
1138
  state.sqlExecutions.set(exec.id, exec);
1113
1139
  // A restarted execution carries a new plan: its next end must resolve it again.
1114
1140
  state.resolvedPlanExecutions.delete(exec.id);
@@ -1295,13 +1321,32 @@ const KNOWN_EVENT_TYPES = new Set(
1295
1321
  );
1296
1322
 
1297
1323
  // SQLExecutionStart and SQLAdaptiveExecutionUpdate carry `physicalPlanDescription`, Spark's text
1298
- // rendering of the plan. Nothing reads it (the plan tree comes from sparkPlanInfo), yet on a real
1299
- // 3.5 GB log it was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1324
+ // rendering of the plan. The plan tree comes from sparkPlanInfo, yet on a real 3.5 GB log the
1325
+ // description was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1300
1326
  // string value out before JSON.parse halves the parse cost of those lines.
1327
+ //
1328
+ // One exception: a Delta write command's node prints only its bare name in sparkPlanInfo, and its
1329
+ // target table appears only on its `Arguments:` line of the description. When the description is
1330
+ // that of one of KEPT_ARGUMENTS_COMMANDS (checked on its first bytes, so no other line pays for it),
1331
+ // the value is cut down to that one line, never kept whole. A value that spans decoder chunks is
1332
+ // still dropped unread (buildChunkDecoder), so the write target then falls back to its child paths.
1301
1333
  const SQL_UI_EVENT_PREFIX = '{"Event":"org.apache.spark.sql.execution.ui.SparkListenerSQL';
1302
1334
  const PLAN_DESCRIPTION_KEY = '"physicalPlanDescription":"';
1303
1335
 
1304
- const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a;
1336
+ const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a, LOWER_N = 0x6e;
1337
+
1338
+ // Delta commands whose root node carries no target in sparkPlanInfo. The description of a
1339
+ // command execution starts `== Physical Plan ==\nExecute <Command> (1)` (JSON-escaped newline).
1340
+ const KEPT_ARGUMENTS_COMMANDS = new Set(DELTA_COMMANDS);
1341
+ // Those whose description is just the command (no child plan printed under it), so it is small
1342
+ // enough to decode whole when it spans decoder chunks. SaveIntoDataSourceCommand prints the whole
1343
+ // query under it, which can be as large as any other plan description.
1344
+ const SMALL_DESCRIPTION_COMMANDS = new Set(
1345
+ [...KEPT_ARGUMENTS_COMMANDS].filter((command) => command !== 'SaveIntoDataSourceCommand'),
1346
+ );
1347
+ const PLAN_HEADER = '== Physical Plan ==\\nExecute ';
1348
+ // The kept form once JSON-parsed: `<Command>\nArguments: <line>`.
1349
+ const KEPT_ARGUMENTS = /^[A-Za-z]+\nArguments: /;
1305
1350
 
1306
1351
  // Index of the closing quote of the JSON string whose content starts at `valueStart`: the first
1307
1352
  // quote preceded by an even number of backslashes. -1 when the string is unterminated.
@@ -1326,7 +1371,48 @@ function closingQuoteAt(buf , limit , carried )
1326
1371
  return -1;
1327
1372
  }
1328
1373
 
1329
- // Returns `line` with the physicalPlanDescription string value emptied, or `line` unchanged when
1374
+ // The command named by a description that starts at text[valueStart] with PLAN_HEADER, when text
1375
+ // holds the whole name; null otherwise.
1376
+ function describedCommand(text , valueStart ) {
1377
+ if (!text.startsWith(PLAN_HEADER, valueStart)) return null;
1378
+ const nameStart = valueStart + PLAN_HEADER.length;
1379
+ let nameEnd = nameStart;
1380
+ while (nameEnd < text.length && /[A-Za-z]/.test(text[nameEnd])) nameEnd++;
1381
+ return nameEnd < text.length ? text.slice(nameStart, nameEnd) : null;
1382
+ }
1383
+
1384
+ // What to do with an open description value whose first bytes are text[valueStart...]: 'keep' it
1385
+ // (a small Delta command description), 'skip' it, or 'wait' when too little has arrived to tell.
1386
+ function descriptionDisposition(text , valueStart ) {
1387
+ const seen = text.slice(valueStart, valueStart + PLAN_HEADER.length);
1388
+ if (!PLAN_HEADER.startsWith(seen)) return 'skip';
1389
+ if (seen.length < PLAN_HEADER.length) return 'wait';
1390
+ const command = describedCommand(text, valueStart);
1391
+ if (command === null) return 'wait'; // the command name is still arriving
1392
+ return SMALL_DESCRIPTION_COMMANDS.has(command) ? 'keep' : 'skip';
1393
+ }
1394
+
1395
+ // The `<Command>\nArguments: <line>` part (still JSON-escaped) of the root command of a plan
1396
+ // description whose value spans line[valueStart, quote), or null when it is not one of
1397
+ // KEPT_ARGUMENTS_COMMANDS. SaveIntoDataSourceCommand is kept only for the Delta source: its other
1398
+ // providers (JDBC) print option maps that this tool has no use for.
1399
+ function keptCommandArguments(line , valueStart , quote ) {
1400
+ const command = describedCommand(line, valueStart);
1401
+ if (command === null || !KEPT_ARGUMENTS_COMMANDS.has(command)) return null;
1402
+ const key = `${command}\\nArguments: `;
1403
+ const at = line.indexOf(key, valueStart + PLAN_HEADER.length + command.length);
1404
+ if (at === -1 || at >= quote) return null;
1405
+ // The line ends at the first escaped newline: a backslash starts a two-char escape.
1406
+ let end = at + key.length;
1407
+ while (end < quote && !(line.charCodeAt(end) === BACKSLASH && line.charCodeAt(end + 1) === LOWER_N)) {
1408
+ end += line.charCodeAt(end) === BACKSLASH ? 2 : 1;
1409
+ }
1410
+ const kept = line.slice(at, Math.min(end, quote));
1411
+ return command === 'SaveIntoDataSourceCommand' && !kept.includes('DeltaDataSource') ? null : kept;
1412
+ }
1413
+
1414
+ // Returns `line` with the physicalPlanDescription string value emptied (or cut to the kept
1415
+ // arguments line, see keptCommandArguments), or `line` unchanged when
1330
1416
  // the key isn't found in Spark's compact form. The key pattern can't match inside another JSON
1331
1417
  // string: there its quotes would be backslash-escaped. buildChunkDecoder already empties a value
1332
1418
  // that spans chunks, so here the value is empty or within one chunk.
@@ -1339,7 +1425,7 @@ export function stripPlanDescription(line ) {
1339
1425
  const quote = closingQuoteIndex(line, valueStart);
1340
1426
  // Unterminated string (a truncated line): leave it for JSON.parse to reject.
1341
1427
  if (quote === -1) return line;
1342
- return line.slice(0, valueStart) + line.slice(quote);
1428
+ return line.slice(0, valueStart) + (keptCommandArguments(line, valueStart, quote) ?? '') + line.slice(quote);
1343
1429
  }
1344
1430
 
1345
1431
  const TASK_END_PREFIX = '{"Event":"SparkListenerTaskEnd",';
@@ -321,13 +321,15 @@ export const TaskEndEventSchema = z.object({
321
321
  export const SqlExecutionStartEventSchema = z.object({
322
322
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart'),
323
323
  executionId: z.number(),
324
+ rootExecutionId: z.number().optional(),
324
325
  description: z.string().optional(),
326
+ physicalPlanDescription: z.string().optional(),
325
327
  time: z.number(),
326
328
  sparkPlanInfo: SparkPlanInfoFieldSchema,
327
329
  });
328
330
 
329
331
  // applyAdaptiveExecutionUpdate: AQE re-plans mid-query and re-emits sparkPlanInfo for the same
330
- // executionId; last write wins. Neither SQL schema declares physicalPlanDescription: nothing reads
332
+ // executionId; last write wins. This schema does not declare physicalPlanDescription: nothing reads
331
333
  // it, so zod strips it (dispatchLine already empties it before JSON.parse, see stripPlanDescription).
332
334
  export const SqlAdaptiveExecutionUpdateEventSchema = z.object({
333
335
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLAdaptiveExecutionUpdate'),
@@ -405,13 +405,13 @@ function verdictJson(model ) {
405
405
  // A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
406
406
  // Each cache is split first by the overrides object the report ran under (one fixed, frozen object
407
407
  // per CLI invocation or MCP server process; DEFAULT_THRESHOLDS for the specification's).
408
-
408
+
409
409
  const DEFAULT_THRESHOLDS = {};
410
410
  const jsonCache = new WeakMap();
411
411
  // The redacted report, keyed by the unredacted appModel it was built from.
412
412
  const redactedJsonCache = new WeakMap();
413
413
 
414
- function cacheFor(cache , thresholds ) {
414
+ function cacheFor (cache , thresholds ) {
415
415
  const key = thresholds ?? DEFAULT_THRESHOLDS;
416
416
  let byModel = cache.get(key);
417
417
  if (!byModel) {
@@ -421,14 +421,23 @@ function cacheFor(cache , thresholds
421
421
  return byModel;
422
422
  }
423
423
 
424
- function runFindings(appModel , thresholds ) {
424
+ const findingsCache = new WeakMap();
425
+
426
+ /** The run's detector catalog and config audit, computed once per run and overrides so the report
427
+ * and the blocks built beside it (run-output.ts) share one analysis. */
428
+ export function runFindings(appModel , thresholds ) {
429
+ const cache = cacheFor(findingsCache, thresholds);
430
+ const cached = cache.get(appModel);
431
+ if (cached) return cached;
425
432
  const { app, stages, executors, sql, jobs, runAggregates } = appModel;
426
433
  const catalog = analyze(
427
434
  app, stages, executors?.added ?? [], executors?.removed ?? [],
428
435
  jobs ?? new Map(), sql ?? new Map(),
429
436
  runAggregates ?? null, { thresholds },
430
437
  );
431
- return { catalog, config: auditConfig(app) };
438
+ const result = { catalog, config: auditConfig(app) };
439
+ cache.set(appModel, result);
440
+ return result;
432
441
  }
433
442
 
434
443
  // Redacts the model and findings before the report derives any text from them, the same order
@@ -6,6 +6,7 @@ import {
6
6
  } from './mcp-tools.js';
7
7
  import { listRuns } from './list-runs.js';
8
8
 
9
+ import { MAX_NORMALIZE_PATTERN_LENGTH, MAX_NORMALIZE_PATTERNS } from './stage-alignment.js';
9
10
 
10
11
  const sourceSchema = z.union([
11
12
  z.object({ path: z.string() }),
@@ -71,7 +72,7 @@ export function createMcpServer({ thresholds }
71
72
  }, (params) => toolResult(listRuns(params)));
72
73
 
73
74
  server.registerTool('diagnose_run', {
74
- description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, and the checks the log lacked the data to run. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
75
+ description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, the checks the log lacked the data to run, and the writeTargets, metrics and effectiveConf blocks of the CLI report. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
75
76
  inputSchema: {
76
77
  ...runRefSchema, redact: z.boolean().optional(),
77
78
  include: z.array(z.enum(['summary', 'evidenceAvailability', 'detectors'])).optional(),
@@ -91,15 +92,19 @@ export function createMcpServer({ thresholds }
91
92
  ));
92
93
 
93
94
  server.registerTool('compare_runs', {
94
- description: 'Compare two runs: categorized findings delta and metric deltas.',
95
+ description: 'Compare two runs: the verdict, metric deltas (metrics), categorized findings delta (findings) and the stage pairs (stagePairs) with the share of executor run time they cover (runtimeCoverage). confidence is ok, low or insufficient. metricDeltas and findingsDelta repeat metrics and findings under their earlier names and will be removed.',
95
96
  inputSchema: {
96
97
  runIdA: z.string().optional(), sourceA: sourceSchema.optional(),
97
98
  ...secondRunRefSchema,
98
99
  redact: z.boolean().optional(),
100
+ include: z.array(z.enum(['stagePairs'])).optional()
101
+ .describe('Extra blocks to return. stagePairs: one row per paired stage with its deltas, which can be large.'),
102
+ normalizePath: z.array(z.string().max(MAX_NORMALIZE_PATTERN_LENGTH)).max(MAX_NORMALIZE_PATTERNS).optional()
103
+ .describe('Regular expressions whose every match in a plan node\'s text is replaced with a fixed token before stages are paired, so run-specific text (an output directory that differs per run) does not keep the same stage from pairing. A generic pattern, not only for paths. Affects stage pairing, runtimeCoverage and confidence, never findings. A pattern that backtracks catastrophically can stall the server: keep them simple and anchored.'),
99
104
  ...formatSchema,
100
105
  },
101
- }, ({ runIdA, sourceA, runIdB, sourceB, redact, format }) => toolResultWithMarkdown(
102
- compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds }),
106
+ }, ({ runIdA, sourceA, runIdB, sourceB, redact, include, normalizePath, format }) => toolResultWithMarkdown(
107
+ compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds, normalizePath, include }),
103
108
  ));
104
109
 
105
110
  server.registerTool('evaluate_budgets', {
@@ -112,14 +117,16 @@ export function createMcpServer({ thresholds }
112
117
  sourceB: sourceSchema.optional().describe('Optional candidate run, compared against source/runId as the regression baseline (maxRegressionPct/failOnIntroduced). When given, the absolute budgets (maxRuntimeMs etc.) are evaluated on this run.'),
113
118
  runIdB: secondRunRefSchema.runIdB.describe('Same as `sourceB`, referencing an already-resolved run by id.'),
114
119
  maxRegressionPct: z.number().optional(), regressionMetric: z.string().optional(), failOnIntroduced: z.string().optional(),
120
+ regressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
121
+ .describe('Further regression budgets, one per metric (the CLI\'s repeated --regression-budget). A metric can be budgeted once across this list and maxRegressionPct/regressionMetric.'),
115
122
  },
116
123
  }, ({
117
124
  source, runId, runIdB, sourceB,
118
125
  maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct,
119
- maxRegressionPct, regressionMetric, failOnIntroduced,
126
+ maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets,
120
127
  }) => toolResult(evaluateBudgetsForRun(
121
128
  { source, runId },
122
- { maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced },
129
+ { maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets },
123
130
  (runIdB || sourceB) ? { runId: runIdB, source: sourceB } : undefined,
124
131
  { thresholds },
125
132
  )));