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
@@ -0,0 +1,262 @@
1
+ // The stage aligner: two snapshots and options in, typed stage pairs out. Pure (no I/O, no
2
+ // dependency on the CLI, MCP or UI) and deterministic; swapping baseline and candidate mirrors the
3
+ // pairs. `compareRuns` calls it, and every surface that compares runs reads its result
4
+ // (`stagePairs`) instead of comparing stage keys.
5
+ //
6
+ // Pairing keys on `comparisonIdentity`: the `stageIdentity` recipe run with the comparison
7
+ // normalizer, so the volatile tokens that make two runs of one job differ (staging directories,
8
+ // dates, IN-lists, Delta log file counts) and the caller's `--normalize-path`
9
+ // patterns do not split a stage in two. `stageIdentity` itself stays the frozen exact key.
10
+
11
+ import { normalizeDetail } from './detectors.js';
12
+ import { isDeltaLogRead, scanRelationId } from './plan-summary.js';
13
+ import { withEarlierAttempts, totalExecutorCpuMs } from './run-totals.js';
14
+ import { planNodesOfStage } from './stage-plan-nodes.js';
15
+ import { identityIndexWith, pairEqualCounts, stageIdentityWith, } from './stage-identity.js';
16
+
17
+
18
+
19
+ /** Version of the comparison block (`stagePairs`, `unmatched`, `replanned`, `bookkeepingStageIds`,
20
+ * `runtimeCoverage`). Additive changes keep it; a consumer refuses a version it does not know. */
21
+ export const COMPARISON_SCHEMA_VERSION = 1;
22
+
23
+ // ---- Comparison normalizer ------------------------------------------------------------------
24
+
25
+ // Targeted patches for the volatile tokens that differ between two runs of one job. Each applies
26
+ // on top of `normalizeDetail`, in this order. No blanket numeric or path stripping: those fuse
27
+ // distinct stages of one run.
28
+ export const COMPARISON_PATCHES = [
29
+ // Delta log and file-index file counts track how many files or commits exist, not the query:
30
+ // DeltaLogFileIndex(4 paths) vs DeltaLogFileIndex(8 paths).
31
+ { name: 'fileCounts', apply: (s) => s.replace(/FileIndex\(\d+ paths\)/g, 'FileIndex(n paths)') },
32
+ // Date and timestamp literals: the run's processing window.
33
+ { name: 'dates', apply: (s) => s.replace(/\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)?/g, 'DATE') },
34
+ // IN-lists grow with the window. Spark prints small lists as `IN (a, b)` and large ones
35
+ // (InSet) as `INSET a, b` with no opening parenthesis.
36
+ {
37
+ name: 'inLists',
38
+ apply: (s) => s.replace(/\bIN \([^()]*\)/g, 'IN (...)').replace(/\bINSET [^()]*?(?=\))/g, 'INSET ...'),
39
+ },
40
+ // Random per-run staging directory segments (`/ds/aB3xYz/`): a 6-8 character alphanumeric path
41
+ // segment that mixes cases, or letters with digits. An ALL-CAPS or all-lowercase name survives,
42
+ // but a real directory that looks like that (`/output01/`, `/Region3/`) is rewritten too.
43
+ {
44
+ name: 'stagingDirs',
45
+ apply: (s) => s.replace(/\/([A-Za-z0-9]{6,8})(?=\/)/g, (m, seg ) =>
46
+ (/[A-Z]/.test(seg) && /[a-z]/.test(seg)) || (/[A-Za-z]/.test(seg) && /\d/.test(seg)) ? '/TMP' : m),
47
+ },
48
+ ];
49
+
50
+ /** What every match of a caller-supplied pattern is replaced with. */
51
+ export const NORMALIZE_PATH_TOKEN = 'NORMALIZED';
52
+ /** Longest caller pattern accepted. A pattern from an agent can backtrack catastrophically, so
53
+ * the length is capped; the cap does not make a bad pattern safe. */
54
+ export const MAX_NORMALIZE_PATTERN_LENGTH = 200;
55
+ export const MAX_NORMALIZE_PATTERNS = 16;
56
+
57
+ /** Compiles caller-supplied normalize patterns (global, every match replaced). Throws an Error
58
+ * naming the offending pattern when one is too long, not a valid regular expression, or matches
59
+ * the empty string (which would insert the token between every character). */
60
+ export function compileNormalizePatterns(patterns ) {
61
+ if (!patterns || patterns.length === 0) return [];
62
+ if (patterns.length > MAX_NORMALIZE_PATTERNS) {
63
+ throw new Error(`At most ${MAX_NORMALIZE_PATTERNS} normalize patterns are accepted, got ${patterns.length}.`);
64
+ }
65
+ return patterns.map((source) => {
66
+ if (typeof source !== 'string' || source.length === 0) throw new Error('A normalize pattern must be a non-empty string.');
67
+ if (source.length > MAX_NORMALIZE_PATTERN_LENGTH) {
68
+ throw new Error(`Normalize pattern "${source.slice(0, 40)}..." is ${source.length} characters; the limit is ${MAX_NORMALIZE_PATTERN_LENGTH}.`);
69
+ }
70
+ let re ;
71
+ try {
72
+ re = new RegExp(source, 'g');
73
+ } catch (e) {
74
+ throw new Error(`Normalize pattern "${source}" is not a valid regular expression: ${(e ).message}`);
75
+ }
76
+ if (re.test('')) throw new Error(`Normalize pattern "${source}" matches the empty string.`);
77
+ re.lastIndex = 0;
78
+ return re;
79
+ });
80
+ }
81
+
82
+ /** The detail normalizer the comparison keys use: caller patterns first, on the text as Spark
83
+ * printed it, then `normalizeDetail`, then the targeted patches. */
84
+ export function comparisonDetailNormalizer(callerPatterns = []) {
85
+ return (detail) => {
86
+ let s = detail;
87
+ for (const re of callerPatterns) s = s.replace(re, NORMALIZE_PATH_TOKEN);
88
+ s = normalizeDetail(s);
89
+ for (const patch of COMPARISON_PATCHES) s = patch.apply(s);
90
+ return s;
91
+ };
92
+ }
93
+
94
+ // ---- Identity and bookkeeping ---------------------------------------------------------------
95
+
96
+ /** `stageIdentity`'s recipe (normalized stage name plus the attributed nodes' fingerprint) with the
97
+ * comparison normalizer. Internal to the aligner: consumers key on a pair's `pairId`. */
98
+ export function comparisonIdentity(
99
+ stage , snapshot , normalizer , nodes ,
100
+ ) {
101
+ return stageIdentityWith(stage, snapshot, normalizer, nodes);
102
+ }
103
+
104
+ function isScanNode(node ) {
105
+ return /^(Scan|BatchScan)\b/.test(node.name ?? '') || /FileScan|JDBCRelation/.test(node.detail ?? '');
106
+ }
107
+
108
+ /** True for a stage whose scans only read Delta log files or checkpoints: it has at least one scan
109
+ * and every scan is internal Delta metadata. Such a stage is table bookkeeping, not query work.
110
+ * A stage with no attributed plan nodes has no scans to test, so it is never flagged. */
111
+ export function isDeltaBookkeepingStage(
112
+ stage , sql , nodes = planNodesOfStage(stage, sql),
113
+ ) {
114
+ let scans = 0;
115
+ for (const node of nodes) {
116
+ if (!isScanNode(node)) continue;
117
+ const name = node.name ?? '', detail = node.detail ?? '';
118
+ if (scanRelationId(name, detail) !== null || !isDeltaLogRead(name, detail)) return false;
119
+ scans++;
120
+ }
121
+ return scans > 0;
122
+ }
123
+
124
+ // ---- Alignment ------------------------------------------------------------------------------
125
+
126
+ export const PAIR_DELTA_METRICS = [
127
+ 'executorRunTime', 'executorCpuTime', 'memoryBytesSpilled', 'diskBytesSpilled',
128
+ 'inputBytes', 'outputBytes', 'shuffleReadBytes', 'shuffleWriteBytes',
129
+ ] ;
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+ // Sum of a field over the stage's own record and the work its figures leave out (earlier
164
+ // attempts, a failed attempt's late tasks), the same all-attempt sums the run totals use.
165
+ // null when no row carried a finite value, so an unrecorded field is not a false 0.
166
+ function attemptSum(rows , field ) {
167
+ let sum = 0, present = false;
168
+ for (const row of rows) {
169
+ const v = row[field];
170
+ if (typeof v === 'number' && Number.isFinite(v)) { sum += v; present = true; }
171
+ }
172
+ return present ? sum : null;
173
+ }
174
+
175
+ function stageFigures(stage ) {
176
+ const rows = withEarlierAttempts([stage]);
177
+ return {
178
+ executorRunTime: attemptSum(rows, 'executorRunTime'),
179
+ executorCpuTime: totalExecutorCpuMs(rows),
180
+ memoryBytesSpilled: attemptSum(rows, 'memoryBytesSpilled'),
181
+ diskBytesSpilled: attemptSum(rows, 'diskBytesSpilled'),
182
+ inputBytes: attemptSum(rows, 'inputBytes'),
183
+ outputBytes: attemptSum(rows, 'outputBytes'),
184
+ shuffleReadBytes: attemptSum(rows, 'shuffleReadBytes'),
185
+ shuffleWriteBytes: attemptSum(rows, 'shuffleWriteBytes'),
186
+ };
187
+ }
188
+
189
+ const ascending = (a , b ) => a - b;
190
+
191
+ /** Pairs the stages of two runs. At this stage the pairing is `matchStages`' algorithm over
192
+ * `comparisonIdentity`: stages sharing an identity pair off by sorted id when each run has the same
193
+ * count of them, and every pair is `exact`. */
194
+ export function alignStages(baseSnap , candSnap , options = {}) {
195
+ const normalizer = comparisonDetailNormalizer(options.normalizePath);
196
+ // One walk of the plan per stage serves the bookkeeping test and the identity.
197
+ const index = (snap ) => {
198
+ const nodesOf = new Map ();
199
+ const bookkeeping = new Set ();
200
+ for (const [id, stage] of snap.stages) {
201
+ const nodes = planNodesOfStage(stage, snap.sql);
202
+ nodesOf.set(id, nodes);
203
+ if (isDeltaBookkeepingStage(stage, snap.sql, nodes)) bookkeeping.add(id);
204
+ }
205
+ const identities = identityIndexWith(
206
+ snap, (stage, id) => comparisonIdentity(stage, snap, normalizer, nodesOf.get(id)), (id) => !bookkeeping.has(id),
207
+ );
208
+ return { identities, bookkeeping };
209
+ };
210
+ const base = index(baseSnap), cand = index(candSnap);
211
+ const baseBook = base.bookkeeping, candBook = cand.bookkeeping;
212
+ const { pairs: idPairs } = pairEqualCounts(base.identities, cand.identities);
213
+
214
+ const baseFigures = new Map ();
215
+ const candFigures = new Map ();
216
+ const figures = (cache , snap , id ) => {
217
+ let f = cache.get(id);
218
+ if (!f) { f = stageFigures(snap.stages.get(id) ); cache.set(id, f); }
219
+ return f;
220
+ };
221
+
222
+ const pairs = idPairs
223
+ .map(({ baseId, candId }) => {
224
+ const b = figures(baseFigures, baseSnap, baseId), c = figures(candFigures, candSnap, candId);
225
+ const deltas = {} ;
226
+ for (const metric of PAIR_DELTA_METRICS) {
227
+ const [bv, cv] = [b[metric], c[metric]];
228
+ deltas[metric] = { baseline: bv, candidate: cv, delta: bv != null && cv != null ? cv - bv : null };
229
+ }
230
+ return { pairId: `b${baseId}-c${candId}`, baseStageIds: [baseId], candStageIds: [candId], quality: 'exact', score: 1, deltas };
231
+ })
232
+ .sort((x, y) => x.baseStageIds[0] - y.baseStageIds[0] || x.candStageIds[0] - y.candStageIds[0]);
233
+
234
+ const pairedBase = new Set(pairs.flatMap((p) => p.baseStageIds));
235
+ const pairedCand = new Set(pairs.flatMap((p) => p.candStageIds));
236
+ const unmatchedOf = (snap , book , paired ) =>
237
+ [...snap.stages.keys()].filter((id) => !book.has(id) && !paired.has(id)).sort(ascending);
238
+
239
+ // Run time counts every attempt, the same figure the stage-pair deltas and the run totals use.
240
+ const runTime = (cache , snap , id ) => figures(cache, snap, id).executorRunTime ?? 0;
241
+ let total = 0, matched = 0;
242
+ for (const id of baseSnap.stages.keys()) {
243
+ if (baseBook.has(id)) continue;
244
+ const t = runTime(baseFigures, baseSnap, id);
245
+ total += t;
246
+ if (pairedBase.has(id)) matched += t;
247
+ }
248
+ for (const id of candSnap.stages.keys()) {
249
+ if (candBook.has(id)) continue;
250
+ const t = runTime(candFigures, candSnap, id);
251
+ total += t;
252
+ if (pairedCand.has(id)) matched += t;
253
+ }
254
+
255
+ return {
256
+ pairs,
257
+ unmatched: { baseStageIds: unmatchedOf(baseSnap, baseBook, pairedBase), candStageIds: unmatchedOf(candSnap, candBook, pairedCand) },
258
+ replanned: [],
259
+ bookkeepingStageIds: { baseStageIds: [...baseBook].sort(ascending), candStageIds: [...candBook].sort(ascending) },
260
+ runtimeCoverage: total > 0 ? matched / total : null,
261
+ };
262
+ }
@@ -0,0 +1,112 @@
1
+ import { cyrb53 } from './string-hash.js';
2
+ import { planNodesOfStage } from './stage-plan-nodes.js';
3
+
4
+
5
+
6
+ // The stage-identity recipe, parameterized by the detail normalizer. `stageIdentity` (the frozen
7
+ // exact key, run-comparison.ts) runs it with `normalizeDetail`; the aligner's `comparisonIdentity`
8
+ // runs it with the comparison normalizer. One recipe, so the two keys can differ only in how a
9
+ // detail string is normalized.
10
+
11
+
12
+
13
+ // Replace run-varying tokens (digit runs, long hex ids) with a stable marker so
14
+ // the same logical stage across two runs normalizes to one identity.
15
+ export function normalizeStageName(name ) {
16
+ return String(name)
17
+ .toLowerCase()
18
+ .replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
19
+ .replace(/\d+/g, '#')
20
+ .replace(/\s+/g, ' ')
21
+ .trim();
22
+ }
23
+
24
+ // Bottom-up, order-independent structural identity of a resolved plan tree:
25
+ // each node folds its normalized name/detail with its children's digests
26
+ // (children sorted, so AQE picking a different broadcast side still matches),
27
+ // so two plans collide only when their whole shape and every node's detail
28
+ // agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
29
+ // codegen numbers, AQE build-side choice, commutative-operand order). Each
30
+ // node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
31
+ // detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
32
+ // instead of embedding full child identity strings, which would re-escape
33
+ // every level below and blow identity size up to ~2^depth on the 20-60+
34
+ // operator-deep plans real Spark produces.
35
+ function planTreeIdentity(root , normalizeDetail ) {
36
+ if (!root) return null;
37
+ function visit(node ) {
38
+ const childDigests = (node.children ?? []).map(visit).sort();
39
+ return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
40
+ }
41
+ return visit(root);
42
+ }
43
+
44
+ // Plan identity for the stage's SQL execution, scoped to only the plan nodes
45
+ // this stage actually ran (`node.stageIds`), not the whole tree: two stages
46
+ // sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
47
+ // collapse onto one identity regardless of which part of the plan each ran.
48
+ // Falls back to the coarser whole-tree identity when the stage has no
49
+ // attributed nodes (hand-built snapshots without `stageIds`, or unmatched
50
+ // accumulables).
51
+ function sqlNodeIdentity(
52
+ stage , snapshot , normalizeDetail , nodes ,
53
+ ) {
54
+ const execId = stage.sqlExecutionId;
55
+ if (execId == null) return '';
56
+ const root = snapshot.sql.get(execId)?.planTree ?? null;
57
+ if (!root) return '';
58
+ const fingerprints = (nodes ?? planNodesOfStage(stage, snapshot.sql))
59
+ .map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
60
+ if (fingerprints.length === 0) return planTreeIdentity(root, normalizeDetail) ?? '';
61
+ return cyrb53(JSON.stringify(fingerprints.sort()));
62
+ }
63
+
64
+ /** `nodes`: the stage's attributed plan nodes when the caller already has them (`planNodesOfStage`),
65
+ * so one walk of the plan serves several readers. */
66
+ export function stageIdentityWith(
67
+ stage , snapshot , normalizeDetail , nodes ,
68
+ ) {
69
+ return normalizeStageName(stage.name ?? '') + '§' + sqlNodeIdentity(stage, snapshot, normalizeDetail, nodes);
70
+ }
71
+
72
+ export function identityIndexWith(
73
+ snapshot , identityOf , only = () => true,
74
+ ) {
75
+ const byIdentity = new Map ();
76
+ for (const [id, stage] of snapshot.stages) {
77
+ if (!only(id)) continue;
78
+ const key = identityOf(stage, id);
79
+ const ids = byIdentity.get(key);
80
+ if (ids) ids.push(id);
81
+ else byIdentity.set(key, [id]);
82
+ }
83
+ return byIdentity;
84
+ }
85
+
86
+ /** Pairs the stages of two identity indexes. An identity colliding equally on both sides has no
87
+ * genuine ambiguity about *count*, so its stages pair off positionally by sorted id rather than
88
+ * being dropped. This is exact when the identity actually distinguishes stages (e.g.
89
+ * self-comparing a run: every stage matches itself). It's a best-effort guess when the identity is
90
+ * coarse (no SQL/attribution) and the two sides are genuinely different runs -- two unrelated
91
+ * same-named stages could get cross-paired. Accepted tradeoff: dropping them instead would also
92
+ * sacrifice the exact-self-comparison case, which matters more. An identity with different counts
93
+ * per side pairs nothing. Deterministic and symmetric: identities visit in sorted order. */
94
+ export function pairEqualCounts(
95
+ baseIdx , candIdx ,
96
+ ) {
97
+ const pairs = [];
98
+ const matchedIdentities = new Set ();
99
+ for (const identity of [...baseIdx.keys()].sort()) {
100
+ const candIds = candIdx.get(identity);
101
+ if (!candIds) continue;
102
+ const baseIds = baseIdx.get(identity) ;
103
+ if (baseIds.length !== candIds.length) continue;
104
+ const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
105
+ const sortedCandIds = [...candIds].sort((a, b) => a - b);
106
+ for (let i = 0; i < sortedBaseIds.length; i++) {
107
+ pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
108
+ }
109
+ matchedIdentities.add(identity);
110
+ }
111
+ return { pairs, matchedIdentities };
112
+ }
@@ -0,0 +1,48 @@
1
+ // Argument and identifier helpers shared by the write-target parsers (write-targets.ts) and the
2
+ // Delta command resolver (delta-targets.ts).
3
+ const IDENT_PART = String.raw`(?:\`(?:[^\`]|\`\`)*\`|[A-Za-z0-9_$]+)`;
4
+ const IDENTIFIER = new RegExp(`^${IDENT_PART}(?:\\.${IDENT_PART})*$`);
5
+ export const IDENT_PARTS = new RegExp(IDENT_PART, 'g');
6
+ const TRUNCATION_MARKER = /\.\.\.(?:\s*\d+ more fields)?/;
7
+
8
+
9
+
10
+ // Splits at top-level commas, ignoring commas inside (), [], {} and backticks. `terminated` is
11
+ // true when a comma follows the arg: the last arg of a cut-off string is never terminated.
12
+ export function splitArgs(args ) {
13
+ const out = [];
14
+ let depth = 0;
15
+ let quoted = false;
16
+ let start = 0;
17
+ for (let i = 0; i < args.length; i++) {
18
+ const c = args[i];
19
+ if (c === '`') quoted = !quoted;
20
+ else if (quoted) continue;
21
+ else if (c === '(' || c === '[' || c === '{') depth++;
22
+ else if (c === ')' || c === ']' || c === '}') depth--;
23
+ else if (c === ',' && depth === 0) {
24
+ out.push({ text: args.slice(start, i).trim(), terminated: true });
25
+ start = i + 1;
26
+ }
27
+ }
28
+ out.push({ text: args.slice(start).trim(), terminated: false });
29
+ return out;
30
+ }
31
+
32
+ export function isCut(text ) {
33
+ return text === '' || TRUNCATION_MARKER.test(text);
34
+ }
35
+
36
+ // `db`.`t` or db.t -> db.t. A part that itself contains a dot or backtick keeps its quoting.
37
+ export function tableName(ident ) {
38
+ if (!IDENTIFIER.test(ident)) return null;
39
+ const parts = (ident.match(IDENT_PARTS) ?? []).map((p) => {
40
+ if (!p.startsWith('`')) return p;
41
+ const inner = p.slice(1, -1).replace(/``/g, '`');
42
+ return /[.`]/.test(inner) ? p : inner;
43
+ });
44
+ return parts.join('.');
45
+ }
46
+
47
+ /** A write target found without parsing the node's simpleString. */
48
+
@@ -6,10 +6,15 @@
6
6
  // - a target Spark cut short (no delimiter after it, or a "..." marker in it) is unparseable;
7
7
  // - a write-like node outside the known list is reported as an unrecognized write;
8
8
  // - an execution whose plan is not in the model (or whose start event could not be read) is
9
- // listed, never skipped, and the count of unreadable log lines is reported.
9
+ // listed, never skipped, and the count of unreadable log lines is reported;
10
+ // - a Delta target is set only when exactly one table or path is named (delta-targets.ts,
11
+ // api-merge-writes.ts).
10
12
  // Targets are verbatim from the simpleString: nothing is resolved (relative paths, ${var}
11
13
  // placeholders, catalog-relative table names all pass through as the log states them).
12
14
  import { walkPlanTree } from './plan-tree-walk.js';
15
+ import { apiMergeWrites } from './api-merge-writes.js';
16
+ import { indexByRoot, isStagedDeltaWrite, parseDeltaTableV2, resolveDeltaCommandTarget, } from './delta-targets.js';
17
+ import { IDENT_PARTS, isCut, splitArgs, tableName, } from './write-target-args.js';
13
18
 
14
19
 
15
20
 
@@ -67,11 +72,6 @@ const NOT_WRITES = new Set([
67
72
  'UpdateEventTimeWatermarkColumn',
68
73
  ]);
69
74
 
70
- const IDENT_PART = String.raw`(?:\`(?:[^\`]|\`\`)*\`|[A-Za-z0-9_$]+)`;
71
- const IDENTIFIER = new RegExp(`^${IDENT_PART}(?:\\.${IDENT_PART})*$`);
72
- const IDENT_PARTS = new RegExp(IDENT_PART, 'g');
73
- const TRUNCATION_MARKER = /\.\.\.(?:\s*\d+ more fields)?/;
74
-
75
75
  function commandOf(nodeName ) {
76
76
  return nodeName.replace(/^Execute\s+/, '').trim().replace(/Exec$/, '');
77
77
  }
@@ -86,30 +86,6 @@ function isWriteLike(command ) {
86
86
  return (operator.match(/[A-Z][a-z0-9]*/g) ?? []).some((word) => WRITE_WORDS.has(word));
87
87
  }
88
88
 
89
-
90
-
91
- // Splits at top-level commas, ignoring commas inside (), [], {} and backticks. `terminated` is
92
- // true when a comma follows the arg: the last arg of a cut-off string is never terminated.
93
- function splitArgs(args ) {
94
- const out = [];
95
- let depth = 0;
96
- let quoted = false;
97
- let start = 0;
98
- for (let i = 0; i < args.length; i++) {
99
- const c = args[i];
100
- if (c === '`') quoted = !quoted;
101
- else if (quoted) continue;
102
- else if (c === '(' || c === '[' || c === '{') depth++;
103
- else if (c === ')' || c === ']' || c === '}') depth--;
104
- else if (c === ',' && depth === 0) {
105
- out.push({ text: args.slice(start, i).trim(), terminated: true });
106
- start = i + 1;
107
- }
108
- }
109
- out.push({ text: args.slice(start).trim(), terminated: false });
110
- return out;
111
- }
112
-
113
89
  // Text after the command name in the simpleString. Empty when the simpleString does not start
114
90
  // with the command (so nothing after it can be trusted).
115
91
  function argsOf(detail , command ) {
@@ -118,21 +94,6 @@ function argsOf(detail , command ) {
118
94
  return m[2];
119
95
  }
120
96
 
121
- function isCut(text ) {
122
- return text === '' || TRUNCATION_MARKER.test(text);
123
- }
124
-
125
- // `db`.`t` or db.t -> db.t. A part that itself contains a dot or backtick keeps its quoting.
126
- function tableName(ident ) {
127
- if (!IDENTIFIER.test(ident)) return null;
128
- const parts = (ident.match(IDENT_PARTS) ?? []).map((p) => {
129
- if (!p.startsWith('`')) return p;
130
- const inner = p.slice(1, -1).replace(/``/g, '`');
131
- return /[.`]/.test(inner) ? p : inner;
132
- });
133
- return parts.join('.');
134
- }
135
-
136
97
  // First arg of a path-writing command. The next arg must be the boolean that follows it in
137
98
  // InsertIntoHadoopFsRelationCommand, or the static-partition map (INSERT OVERWRITE ... PARTITION
138
99
  // (dt='x')) that precedes that boolean, which Spark prints as `[dt=x]` (or `Map(dt -> x)`); this also rejects a path Spark printed with ", " inside.
@@ -259,9 +220,10 @@ function isKnownWrite(command ) {
259
220
  function parseWrite(command , detail ) {
260
221
  const argParser = ARG_PARSERS[command];
261
222
  if (argParser) return [true, argParser(splitArgs(argsOf(detail, command)))];
262
- if (V2_WRITES.has(command)) return [true, parseV2Write(detail)];
223
+ if (V2_WRITES.has(command)) return [true, parseDeltaTableV2(detail) ?? parseV2Write(detail)];
263
224
  if (V2_TABLE_AS_SELECT.has(command)) return [true, parseV2TableAsSelect(splitArgs(argsOf(detail, command)))];
264
225
  // A MERGE prints its source relation too, so a delta. path in it may be the source, not the target.
226
+ // Its target comes from resolveDeltaCommandTarget alone.
265
227
  if (command === 'MergeIntoCommand') return [true, null];
266
228
  if (DELTA_COMMANDS.has(command)) return [true, parseDeltaPath(splitArgs(argsOf(detail, command)))];
267
229
  return [false, null];
@@ -272,14 +234,16 @@ function outputRowsOf(node ) {
272
234
  return metric !== undefined && Number.isFinite(metric.value) ? metric.value : null;
273
235
  }
274
236
 
275
- function collectWrites(executionId , root , out ) {
237
+ function collectWrites(exec , root , byRoot , out ) {
276
238
  walkPlanTree(root, (node) => {
277
239
  const command = commandOf(node.name);
278
240
  if (!isWriteLike(command)) return;
279
241
  const detail = node.detail ?? '';
280
- const [recognized, parsed] = parseWrite(command, detail);
242
+ const [recognized, parsedFromDetail] = parseWrite(command, detail);
243
+ // A Delta command's simpleString is bare on real logs: its target is found elsewhere.
244
+ const parsed = parsedFromDetail ?? resolveDeltaCommandTarget(command, exec, node, byRoot);
281
245
  out.push({
282
- sqlExecutionId: executionId,
246
+ sqlExecutionId: exec.id,
283
247
  nodeId: node.id ?? null,
284
248
  command,
285
249
  recognized,
@@ -291,6 +255,20 @@ function collectWrites(executionId , root , out )
291
255
  }, { dedupe: true });
292
256
  }
293
257
 
258
+ // A staged Delta write writes the table its root's CTAS/RTAS node names, when it has exactly one.
259
+ function attributeStagedWrites(writes , sql ) {
260
+ for (const write of writes) {
261
+ if (write.target !== null || !isStagedDeltaWrite(write)) continue;
262
+ const root = sql.get(write.sqlExecutionId)?.rootExecutionId;
263
+ if (root === undefined || root === write.sqlExecutionId) continue;
264
+ const parents = writes.filter((w) => w.sqlExecutionId === root && V2_TABLE_AS_SELECT.has(w.command));
265
+ if (parents.length === 1 && parents[0].target !== null) {
266
+ write.kind = parents[0].kind;
267
+ write.target = parents[0].target;
268
+ }
269
+ }
270
+ }
271
+
294
272
 
295
273
 
296
274
 
@@ -301,11 +279,16 @@ export function extractWriteTargets(sql , input
301
279
  const writes = [];
302
280
  const withoutPlan = new Map ();
303
281
  for (const id of input.unreadableSqlExecutions ?? []) if (!sql.get(id)?.planTree) withoutPlan.set(id, 'unreadableStart');
282
+ const byRoot = indexByRoot(sql);
304
283
  for (const id of [...sql.keys()].sort((a, b) => a - b)) {
305
- const planTree = sql.get(id) .planTree;
306
- if (!planTree) withoutPlan.set(id, withoutPlan.get(id) ?? 'noPlan');
307
- else collectWrites(id, planTree, writes);
284
+ const exec = sql.get(id) ;
285
+ if (!exec.planTree) withoutPlan.set(id, withoutPlan.get(id) ?? 'noPlan');
286
+ else collectWrites(exec, exec.planTree, byRoot, writes);
308
287
  }
288
+ // DeltaTable API merges run no command node: they are found by their executions' descriptions.
289
+ attributeStagedWrites(writes, sql);
290
+ writes.push(...apiMergeWrites(sql));
291
+ writes.sort((a, b) => a.sqlExecutionId - b.sqlExecutionId);
309
292
  const executionsWithoutPlan = [...withoutPlan].sort((a, b) => a[0] - b[0])
310
293
  .map(([sqlExecutionId, reason]) => ({ sqlExecutionId, reason }));
311
294
  return { writes, executionsWithoutPlan, skippedLines: input.skippedLines ?? null };