sparkforensics-server 0.4.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 (382) hide show
  1. package/index.js +3 -0
  2. package/package.json +3 -3
  3. package/public/assets/AutoscalingChurn-5hDGIXi4.js +1 -0
  4. package/public/assets/CacheUtilization-CT2SCnJJ.js +1 -0
  5. package/public/assets/CachingOpportunity-D_O_WkbH.js +1 -0
  6. package/public/assets/ColdStart-B38OiAOp.js +1 -0
  7. package/public/assets/ConfigAudit-BoplryCH.js +1 -0
  8. package/public/assets/CoreUsageArea-csgwNxkl.js +1 -0
  9. package/public/assets/Dashboard-BgKodJl2.js +2 -0
  10. package/public/assets/DisclosureContext-B0siMVdg.js +1 -0
  11. package/public/assets/DuplicatePlanSubtree-BqS2A2j4.js +1 -0
  12. package/public/assets/ExecutorUtilization-Dp52QGWo.js +1 -0
  13. package/public/assets/GcPressure-CT1yzR_s.js +1 -0
  14. package/public/assets/IncompleteRun-C7Qkjtjn.js +1 -0
  15. package/public/assets/JobFailures-WHl3ELfh.js +1 -0
  16. package/public/assets/MemoryUtilization-D8IUDHJI.js +1 -0
  17. package/public/assets/OverBroadcast-6oSSAQ_R.js +1 -0
  18. package/public/assets/PartitionHint-CUvbnnxy.js +1 -0
  19. package/public/assets/PartitionSizing-BwdFL-mw.js +1 -0
  20. package/public/assets/{PlanExplorer-DdmPkKO1.js → PlanExplorer-DcxmiQWL.js} +1 -1
  21. package/public/assets/PlanGraphRoute-pqnHTt4i.js +1 -0
  22. package/public/assets/RetryWaste-BTbeHkJc.js +1 -0
  23. package/public/assets/ShuffleIO-CRhrYNE0.js +1 -0
  24. package/public/assets/Skew-DSNXRG9V.js +1 -0
  25. package/public/assets/SlowHost-B7Lwu2EU.js +1 -0
  26. package/public/assets/SmallFiles-C082c2rd.js +1 -0
  27. package/public/assets/SpeculationWaste-BS2vpadr.js +1 -0
  28. package/public/assets/Spill-B4DVKHXx.js +1 -0
  29. package/public/assets/StageFailed-D3ac9Sou.js +1 -0
  30. package/public/assets/StageFindingGroup-D9naK109.js +1 -0
  31. package/public/assets/{StageHeader-BSV8ALhT.js → StageHeader-pI1nTwVf.js} +1 -1
  32. package/public/assets/StageShape-eO5le8iG.js +1 -0
  33. package/public/assets/StageSlowness-BaNyfNvL.js +1 -0
  34. package/public/assets/Straggler-D5Fnok_y.js +1 -0
  35. package/public/assets/TaskFailures-BWnC-K_G.js +1 -0
  36. package/public/assets/{TinyTask-CIPRxWfT.js → TinyTask-8_5lpq_2.js} +1 -1
  37. package/public/assets/UnderBroadcast-DJ0XSwth.js +1 -0
  38. package/public/assets/{finding-anchor-DF1cSHO1.js → finding-anchor-Cys8Z6Yn.js} +1 -1
  39. package/public/assets/{impact-sort-7z0ZcBZf.js → impact-sort-BTQndbof.js} +1 -1
  40. package/public/assets/index-DCeWge12.js +13 -0
  41. package/public/assets/parser-worker-DmeCnLWU.js +122 -0
  42. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  43. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  44. package/public/assets/{select-Cy5pmbrh.js → select-DIYN6D3y.js} +1 -1
  45. package/public/assets/{usePagedRows-BjPOpUU8.js → usePagedRows-BxvdFnPk.js} +1 -1
  46. package/public/assets/useRecentFiles-C3Ov47U7.js +11 -0
  47. package/public/assets/{useSortMode-CU0vzThu.js → useSortMode-B8SRfAoG.js} +1 -1
  48. package/public/docs/404.html +4 -3
  49. package/public/docs/assets/{app.C6w47a9W.js → app.D2GqAkhy.js} +1 -1
  50. package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
  51. package/public/docs/assets/chunks/{VPLocalSearchBox.BcTNvT55.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
  52. package/public/docs/assets/chunks/{theme.D-mE6j02.js → theme.BuxVpb0H.js} +2 -2
  53. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  54. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +1 -0
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  77. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  78. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.lean.js +1 -0
  79. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.lean.js +1 -0
  81. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  82. package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.DOTVHjTV.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  107. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  108. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  109. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +1 -0
  110. package/public/docs/assets/{contributor-guide_contributing.md.B2E9ni79.lean.js → contributor-guide_contributing.md.CQMvGwFt.lean.js} +1 -1
  111. package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.js +15 -0
  112. package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.lean.js +1 -0
  113. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +1 -0
  114. package/public/docs/assets/{contributor-guide_testing.md.DTwhpoN2.lean.js → contributor-guide_testing.md.By6XGR9a.lean.js} +1 -1
  115. package/public/docs/assets/{style.DSixAiZE.css → style.tmDhD3c8.css} +1 -1
  116. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  117. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  118. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  119. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  120. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  121. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  122. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  123. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  124. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +22 -0
  125. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +1 -0
  126. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  127. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  128. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  129. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  130. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  131. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  132. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  133. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.js +55 -0
  135. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js +1 -0
  136. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  137. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  138. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js +26 -0
  139. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js +1 -0
  140. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  141. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  142. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  143. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  144. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  145. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  146. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  147. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  148. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  149. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  150. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.js +1 -0
  151. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
  152. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
  153. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
  154. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +1 -0
  155. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  156. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  157. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  158. package/public/docs/contributor-guide/architecture/board-widgets.html +7 -6
  159. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  160. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  161. package/public/docs/contributor-guide/architecture/detector-contract.html +7 -6
  162. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  163. package/public/docs/contributor-guide/architecture/drill-down.html +7 -6
  164. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  165. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  166. package/public/docs/contributor-guide/architecture/impact-estimation.html +7 -6
  167. package/public/docs/contributor-guide/architecture/index.html +7 -6
  168. package/public/docs/contributor-guide/architecture/overview.html +7 -6
  169. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  170. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  171. package/public/docs/contributor-guide/architecture/state-and-history.html +7 -6
  172. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  173. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  174. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  175. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  176. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  177. package/public/docs/contributor-guide/architecture/widget-rendering.html +7 -6
  178. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  179. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  180. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  181. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  182. package/public/docs/contributor-guide/architecture/worker-protocol.html +7 -11
  183. package/public/docs/contributor-guide/contributing.html +7 -6
  184. package/public/docs/contributor-guide/development-setup.html +11 -10
  185. package/public/docs/contributor-guide/testing.html +7 -6
  186. package/public/docs/hashmap.json +1 -1
  187. package/public/docs/index.html +5 -4
  188. package/public/docs/tuning-reference/anti-patterns.html +5 -4
  189. package/public/docs/tuning-reference/aqe.html +5 -4
  190. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -4
  191. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -4
  192. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -4
  193. package/public/docs/tuning-reference/bottleneck-failures.html +5 -4
  194. package/public/docs/tuning-reference/bottleneck-gc.html +5 -4
  195. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -4
  196. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -4
  197. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -4
  198. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -4
  199. package/public/docs/tuning-reference/bottleneck-skew.html +5 -4
  200. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -4
  201. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -4
  202. package/public/docs/tuning-reference/bottleneck-spill.html +5 -4
  203. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -4
  204. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -4
  205. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -4
  206. package/public/docs/tuning-reference/caching.html +5 -4
  207. package/public/docs/tuning-reference/cluster-config.html +5 -4
  208. package/public/docs/tuning-reference/config.html +5 -4
  209. package/public/docs/tuning-reference/data-formats.html +5 -4
  210. package/public/docs/tuning-reference/index.html +5 -4
  211. package/public/docs/tuning-reference/intro.html +5 -4
  212. package/public/docs/tuning-reference/joins.html +5 -4
  213. package/public/docs/tuning-reference/memory-model.html +5 -4
  214. package/public/docs/tuning-reference/metrics.html +5 -4
  215. package/public/docs/tuning-reference/partitioning.html +5 -4
  216. package/public/docs/tuning-reference/pyspark.html +5 -4
  217. package/public/docs/tuning-reference/shuffle.html +5 -4
  218. package/public/docs/tuning-reference/spark-architecture.html +5 -4
  219. package/public/docs/tuning-reference/table-formats.html +5 -4
  220. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  221. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  222. package/public/docs/user-guide/alternative-log-retrieval.html +11 -7
  223. package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -0
  224. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  225. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  226. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  227. package/public/docs/user-guide/getting-started.html +7 -11
  228. package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -0
  229. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  230. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  231. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  232. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  233. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  234. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  235. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  236. package/public/docs/user-guide/mcp-tools.html +12 -126
  237. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  238. package/public/docs/user-guide/run-comparison.html +7 -6
  239. package/public/docs/user-guide/understanding-findings.html +7 -6
  240. package/public/export-template.html +44 -44
  241. package/public/index.html +4 -4
  242. package/vendor-core/allocation.js +106 -0
  243. package/vendor-core/analyzer.js +13 -13
  244. package/vendor-core/api-merge-writes.js +80 -0
  245. package/vendor-core/cli/budgets.js +23 -9
  246. package/vendor-core/cli/collect-run.js +11 -4
  247. package/vendor-core/cli/regression-budgets.js +83 -0
  248. package/vendor-core/comparison-output.js +49 -0
  249. package/vendor-core/comparison-verdict.js +22 -22
  250. package/vendor-core/core-source-hash.txt +1 -1
  251. package/vendor-core/delta-commands.js +13 -0
  252. package/vendor-core/delta-log-paths.js +42 -0
  253. package/vendor-core/delta-targets.js +127 -0
  254. package/vendor-core/detectors.js +202 -68
  255. package/vendor-core/docs-content/detection/cache.md +3 -2
  256. package/vendor-core/docs-content/detection/cfg.md +9 -8
  257. package/vendor-core/docs-content/detection/chrn.md +1 -2
  258. package/vendor-core/docs-content/detection/cold.md +4 -2
  259. package/vendor-core/docs-content/detection/fail.md +3 -2
  260. package/vendor-core/docs-content/detection/gc.md +3 -2
  261. package/vendor-core/docs-content/detection/host.md +2 -1
  262. package/vendor-core/docs-content/detection/local.md +1 -1
  263. package/vendor-core/docs-content/detection/mem.md +5 -2
  264. package/vendor-core/docs-content/detection/plan.md +2 -1
  265. package/vendor-core/docs-content/detection/sfail.md +2 -1
  266. package/vendor-core/docs-content/detection/shape.md +5 -4
  267. package/vendor-core/docs-content/detection/skew.md +3 -1
  268. package/vendor-core/docs-content/detection/slow.md +2 -2
  269. package/vendor-core/docs-content/detection/spec.md +2 -3
  270. package/vendor-core/docs-content/detection/spill.md +1 -1
  271. package/vendor-core/docs-site-config.js +1 -1
  272. package/vendor-core/effective-conf.js +107 -0
  273. package/vendor-core/efficiency-model.js +8 -6
  274. package/vendor-core/event-handlers.js +255 -56
  275. package/vendor-core/event-schemas.js +5 -1
  276. package/vendor-core/evidence-report.js +31 -14
  277. package/vendor-core/finding-generic-recommendation.js +20 -1
  278. package/vendor-core/finding-names.js +7 -0
  279. package/vendor-core/finding-presentation.js +61 -26
  280. package/vendor-core/finding-tag-help.js +1 -1
  281. package/vendor-core/finding-types.js +12 -0
  282. package/vendor-core/format-utils.js +4 -3
  283. package/vendor-core/impact-estimator.js +20 -2
  284. package/vendor-core/impact-format.js +14 -13
  285. package/vendor-core/impact-model.js +27 -5
  286. package/vendor-core/ingest.js +4 -2
  287. package/vendor-core/list-runs.js +5 -2
  288. package/vendor-core/mcp-server-factory.js +13 -6
  289. package/vendor-core/mcp-tools.js +43 -34
  290. package/vendor-core/model-assembler.js +23 -1
  291. package/vendor-core/parser-worker.js +1 -1
  292. package/vendor-core/plan-graph-model.js +1 -1
  293. package/vendor-core/plan-summary.js +13 -10
  294. package/vendor-core/proxy.js +3 -1
  295. package/vendor-core/python-stage.js +25 -0
  296. package/vendor-core/recommendation-rollup.js +16 -9
  297. package/vendor-core/redact.js +51 -10
  298. package/vendor-core/remediation.js +20 -0
  299. package/vendor-core/run-comparison.js +118 -140
  300. package/vendor-core/run-interpretation.js +2 -1
  301. package/vendor-core/run-metrics.js +198 -0
  302. package/vendor-core/run-output.js +26 -0
  303. package/vendor-core/run-totals.js +24 -0
  304. package/vendor-core/run-verdict.js +3 -4
  305. package/vendor-core/scorecard-estimates.js +1 -0
  306. package/vendor-core/session-snapshot.js +7 -0
  307. package/vendor-core/shs-schemas.js +2 -2
  308. package/vendor-core/spark-memory.js +17 -0
  309. package/vendor-core/stage-alignment.js +262 -0
  310. package/vendor-core/stage-identity.js +112 -0
  311. package/vendor-core/stage-plan-nodes.js +18 -0
  312. package/vendor-core/stage-quantiles.js +4 -0
  313. package/vendor-core/types.js +54 -1
  314. package/vendor-core/wasted-core-hours.js +10 -7
  315. package/vendor-core/write-target-args.js +48 -0
  316. package/vendor-core/write-targets.js +295 -0
  317. package/public/assets/AutoscalingChurn-CUSIvvab.js +0 -1
  318. package/public/assets/CacheUtilization-Ce0K6_X2.js +0 -1
  319. package/public/assets/CachingOpportunity-Cn-LmRA4.js +0 -1
  320. package/public/assets/ColdStart-BoiYzEyV.js +0 -1
  321. package/public/assets/ConfigAudit-BoJc6PAw.js +0 -1
  322. package/public/assets/CoreUsageArea-BFfXKu7K.js +0 -1
  323. package/public/assets/Dashboard-Bo_1jGqH.js +0 -2
  324. package/public/assets/DisclosureContext-LYzHroHO.js +0 -1
  325. package/public/assets/DuplicatePlanSubtree-BaAuGAoo.js +0 -1
  326. package/public/assets/ExecutorUtilization-OE6VMFkG.js +0 -1
  327. package/public/assets/GcPressure-AoL_ZNrV.js +0 -1
  328. package/public/assets/IncompleteRun-Bv6IzgsQ.js +0 -1
  329. package/public/assets/JobFailures-DF3U2z1f.js +0 -1
  330. package/public/assets/MemoryUtilization-B65OVZlq.js +0 -1
  331. package/public/assets/OverBroadcast-DXbJREDl.js +0 -1
  332. package/public/assets/PartitionSizing-DZLnjy0d.js +0 -1
  333. package/public/assets/PlanGraphRoute-C-BGmC6j.js +0 -1
  334. package/public/assets/RetryWaste-BbEPsdMw.js +0 -1
  335. package/public/assets/ShuffleIO-wZB94LS8.js +0 -1
  336. package/public/assets/Skew-SpQE7zeJ.js +0 -1
  337. package/public/assets/SlowHost-DwDBCTvr.js +0 -1
  338. package/public/assets/SmallFiles-yjY_6sT1.js +0 -1
  339. package/public/assets/SpeculationWaste-73PDmj0c.js +0 -1
  340. package/public/assets/Spill-DEw0Kd1i.js +0 -1
  341. package/public/assets/StageFailed-BEfXdEJc.js +0 -1
  342. package/public/assets/StageFindingGroup-Bzm2TY03.js +0 -1
  343. package/public/assets/StageShape-CzR4jHOt.js +0 -1
  344. package/public/assets/StageSlowness-vMPsnAaa.js +0 -1
  345. package/public/assets/Straggler-Cf__OGl5.js +0 -1
  346. package/public/assets/TaskFailures-BZ8kLZiO.js +0 -1
  347. package/public/assets/UnderBroadcast-CBm8ZPoS.js +0 -1
  348. package/public/assets/index-CEM9bz_x.js +0 -13
  349. package/public/assets/parser-worker-Q5KCPdKT.js +0 -122
  350. package/public/assets/useRecentFiles-BrBl-AP1.js +0 -11
  351. package/public/docs/assets/chunks/@localSearchIndexroot.DbZy5EtM.js +0 -1
  352. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.js +0 -1
  353. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.lean.js +0 -1
  354. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.js +0 -1
  355. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.lean.js +0 -1
  356. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.js +0 -1
  357. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.lean.js +0 -1
  358. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.js +0 -1
  359. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.lean.js +0 -1
  360. package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +0 -1
  361. package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +0 -1
  362. package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.js +0 -1
  363. package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.lean.js +0 -1
  364. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DOTVHjTV.js +0 -1
  365. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.js +0 -1
  366. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.lean.js +0 -1
  367. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.js +0 -6
  368. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.lean.js +0 -1
  369. package/public/docs/assets/contributor-guide_contributing.md.B2E9ni79.js +0 -1
  370. package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.js +0 -15
  371. package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.lean.js +0 -1
  372. package/public/docs/assets/contributor-guide_testing.md.DTwhpoN2.js +0 -1
  373. package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.js +0 -1
  374. package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.lean.js +0 -1
  375. package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.js +0 -6
  376. package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.lean.js +0 -1
  377. package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.js +0 -131
  378. package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.lean.js +0 -1
  379. package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.js +0 -1
  380. package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.lean.js +0 -1
  381. package/public/docs/assets/user-guide_understanding-findings.md.D1ccs60C.js +0 -1
  382. /package/public/docs/assets/{user-guide_understanding-findings.md.D1ccs60C.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
@@ -1,7 +1,8 @@
1
1
  // The waste models every detector entry's estimate() builds its ImpactEstimate from: the assumed
2
2
  // throughputs, the per-stage measurements behind them and the occupancy clip wrappers. Each
3
3
  // finding type's own composition of these lives on its DETECTORS entry, next to its detect().
4
-
4
+
5
+ import { isPythonStage } from './python-stage.js';
5
6
  import { nsToMs } from './format-utils.js';
6
7
  import {
7
8
  estimateSingleStage, estimateMultiStage,
@@ -16,6 +17,8 @@ import {
16
17
 
17
18
 
18
19
 
20
+
21
+
19
22
 
20
23
 
21
24
  // Assumed shuffle-network throughput per executor link, ~1 Gbps. Starting assumption, unvalidated.
@@ -104,13 +107,14 @@ const IDLE_CPU_SHARE_MAX = 0.01;
104
107
 
105
108
  // True when the stage's tasks spent under IDLE_CPU_SHARE_MAX of their run time on CPU. False when
106
109
  // the share can't be trusted: no CPU time recorded (older Spark logs omit the metric), or Python
107
- // code run through PythonRDD, whose worker-process CPU executorCpuTime (the JVM task thread's)
108
- // never counts (such stages read 0.1% on the same logs while computing).
109
- export function tasksMostlyIdle(stage ) {
110
+ // code run through a Python worker (isPythonStage: a PythonRDD stage or a Python UDF operator in
111
+ // its SQL plan), whose worker-process CPU executorCpuTime (the JVM task thread's) never counts
112
+ // (such stages read 0.1% on the same logs while computing).
113
+ export function tasksMostlyIdle(stage , sql = new Map()) {
110
114
  const runMs = stage.executorRunTime ?? 0;
111
115
  const cpuMs = nsToMs(stage.executorCpuTime ?? 0);
112
116
  if (runMs <= 0 || cpuMs <= 0) return false;
113
- if (/PythonRDD/.test(stage.name ?? '') || /org\.apache\.spark\.api\.python\./.test(stage.details ?? '')) return false;
117
+ if (isPythonStage(stage, sql)) return false;
114
118
  return cpuMs / runMs < IDLE_CPU_SHARE_MAX;
115
119
  }
116
120
 
@@ -173,3 +177,21 @@ export function stageMappableWasteOrCostOnly(
173
177
  // null: every stage excluded from the sweep
174
178
  return multiStageImpact(stageIds, wasteMsByStage, ctx, 'modeled', rawWaste) ?? costOnly('modeled', rawWaste);
175
179
  }
180
+
181
+ // Finding types whose raw figure is busy core time read straight from the log: gc's jvmGCTime
182
+ // (coreMs) and the discarded speculative or retried attempts' run time ('ms' cross-task sums).
183
+ // Any other core figure is idle capacity, or modeled on an assumed constant (coreLocality's
184
+ // per-task fetch penalty, autoscalingChurn's executor-hours, jobFailureRate's job-hours).
185
+ const MEASURED_CORE_TIME_FIGURE = new Set(['gc', 'retryWaste', 'speculationWaste']);
186
+
187
+ /** The busy core time a finding's fix removes, in core-milliseconds, or null when the detector
188
+ * measures none. Only a measured figure counts: one its estimate() already set (skew and
189
+ * straggler's removed task time), or the raw figure of a MEASURED_CORE_TIME_FIGURE type. A
190
+ * wall-clock claim, an idle capacity figure and a modeled figure are never converted. It never
191
+ * reads executorCpuTime, which leaves out Python worker CPU. */
192
+ export function coreTimeFor(finding , estimate ) {
193
+ if (estimate.coreTimeMs !== undefined) return estimate.coreTimeMs;
194
+ const raw = estimate.rawWaste;
195
+ if (!raw || !MEASURED_CORE_TIME_FIGURE.has(finding.type)) return null;
196
+ return { low: raw.value, high: raw.value };
197
+ }
@@ -14,6 +14,7 @@
14
14
 
15
15
 
16
16
 
17
+
17
18
 
18
19
 
19
20
 
@@ -39,9 +40,10 @@ export function routeMessage(
39
40
  case 'runAggregates': handlers.onRunAggregates?.(data.data); break;
40
41
  case 'stageExecutorMetrics': handlers.onStageExecutorMetrics?.(data.data); break;
41
42
  case 'stageSpeculationWaste': handlers.onStageSpeculationWaste?.(data.data); break;
43
+ case 'stageLateAttemptWork': handlers.onStageLateAttemptWork?.(data.data); break;
42
44
  case 'done': {
43
- const { skippedLines } = data;
44
- handlers.onDone?.({ skippedLines });
45
+ const { skippedLines, unreadableSqlExecutions } = data;
46
+ handlers.onDone?.(unreadableSqlExecutions ? { skippedLines, unreadableSqlExecutions } : { skippedLines });
45
47
  break;
46
48
  }
47
49
  case 'error': handlers.onError?.(data); break;
@@ -5,6 +5,7 @@ import { reassembleRollingEntries } from './parser-worker.js';
5
5
  import { peekLogHeader } from './log-header-peek.js';
6
6
  import { mcpError } from './mcp-error.js';
7
7
  import { normalizeBaseUrl } from './shs-request.js';
8
+ import { isConnectionFailure } from './proxy.js';
8
9
  import { DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_MAX_ARCHIVE_BYTES } from './shs-load.js';
9
10
 
10
11
 
@@ -205,10 +206,12 @@ export async function listRunsShs(
205
206
  let res ;
206
207
  try {
207
208
  // An unresponsive SHS would otherwise hang the tool call forever. A timed-out signal rejects
208
- // the fetch with an AbortError, which this same catch turns into access-or-upstream-failure.
209
+ // the fetch with a TimeoutError, which this same catch turns into upstream-unreachable, the
210
+ // code a refused connection gets too.
209
211
  res = await fetchImpl(url.toString(), { signal: AbortSignal.timeout(DEFAULT_IDLE_TIMEOUT_MS) });
210
212
  } catch (e) {
211
- throw mcpError('access-or-upstream-failure', `Could not reach ${normalized}: ${e instanceof Error ? e.message : String(e)}`);
213
+ const code = isConnectionFailure(e) ? 'upstream-unreachable' : 'access-or-upstream-failure';
214
+ throw mcpError(code, `Could not reach ${normalized}: ${e instanceof Error ? e.message : String(e)}`);
212
215
  }
213
216
  if (!res.ok) {
214
217
  throw mcpError('access-or-upstream-failure', `SHS applications list request failed with status ${res.status}.`);
@@ -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
  )));
@@ -6,13 +6,14 @@ import { collectRun } from './cli/collect-run.js';
6
6
  import { deriveEvidenceAvailability } from './evidence-availability.js';
7
7
  import { resolveFromShs, DEFAULT_MAX_ARCHIVE_BYTES, DEFAULT_IDLE_TIMEOUT_MS } from './shs-load.js';
8
8
  import { mcpError } from './mcp-error.js';
9
- import { buildEvidenceReport, toFindingsFilter, } from './evidence-report.js';
10
- import { redactComparison } from './redact.js';
9
+ import { buildEvidenceReport, runFindings, toFindingsFilter, } from './evidence-report.js';
11
10
  import { computeWallClock } from './wall-clock.js';
12
- import { analyze } from './analyzer.js';
13
- import { buildComparison, renderComparisonMarkdown, } from './run-comparison.js';
14
- import { comparisonVerdict, } from './comparison-verdict.js';
11
+ import { renderComparisonMarkdown, } from './run-comparison.js';
12
+ import { buildComparisonOutput, } from './comparison-output.js';
13
+ import { compileNormalizePatterns } from './stage-alignment.js';
14
+ import { runOutputBlocks } from './run-output.js';
15
15
  import { evaluateBudgets, } from './cli/budgets.js';
16
+ import { combineRegressionBudgets } from './cli/regression-budgets.js';
16
17
  import { FINDING_NAMES, titleCase } from './finding-names.js';
17
18
  import { docAnchorForType } from './detector-docs.js';
18
19
  import { DETECTORS, } from './detectors.js';
@@ -46,19 +47,15 @@ import { typeTag } from './format-utils.js';
46
47
 
47
48
 
48
49
 
49
- // compareRuns returns a smaller MCP-facing projection of CompareRunsResult
50
- // (runIdA/runIdB/verdict/findingsDelta/metricDeltas/confidence/reason/matchedCoverage), not the full
51
- // raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
52
-
50
+ // compareRuns returns the shared comparison projection (comparison-output.ts, the CLI's `comparison`
51
+ // object), not the full raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
52
+ // `verdict` is the dashboard comparison page's headline (baseline = runIdA, candidate = runIdB).
53
+
53
54
 
54
55
 
55
56
 
56
-
57
-
58
-
59
57
 
60
-
61
-
58
+
62
59
 
63
60
 
64
61
  function envInt(name , fallback ) {
@@ -192,6 +189,7 @@ export function diagnoseRun(runId , opts
192
189
 
193
190
 
194
191
 
192
+
195
193
  {
196
194
  const appModel = getCachedAppModel(runId);
197
195
  const findingsFilter = toFindingsFilter(opts?.impactBand, opts?.type, opts?.stageId);
@@ -203,6 +201,9 @@ export function diagnoseRun(runId , opts
203
201
  return {
204
202
  runId, verdict: json.verdict, findings: json.findings, recommendations: json.recommendations, cleanChecks: json.cleanChecks,
205
203
  notRunChecks: json.notRunChecks,
204
+ // The CLI report's own blocks, so a run reads the same through either surface.
205
+ writeTargets: json.writeTargets,
206
+ ...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds }),
206
207
  runComplete: appModel.app?.endTime != null,
207
208
  // Top level too, so a client that never asks for `summary` still sees the run was tuned.
208
209
  ...(tuned ? { tunedThresholds: tuned } : {}),
@@ -345,16 +346,19 @@ async function resolveAndAnalyze(
345
346
  ref , thresholds ,
346
347
  ) {
347
348
  const { runId, appModel } = await resolveOrCreateRun(ref);
348
- const catalog = analyze(
349
- appModel.app, appModel.stages, appModel.executors.added, appModel.executors.removed,
350
- appModel.jobs, appModel.sql, appModel.runAggregates, { thresholds },
351
- );
352
- return { runId, appModel, catalog };
349
+ return { runId, appModel, catalog: runFindings(appModel, thresholds).catalog };
353
350
  }
354
351
 
355
352
  export async function compareRuns(
356
- a , b , opts ,
353
+ a , b , opts
354
+ ,
357
355
  ) {
356
+ // Before any log is read: a bad pattern refuses the call, as the CLI's usage error does.
357
+ try {
358
+ compileNormalizePatterns(opts?.normalizePath);
359
+ } catch (e) {
360
+ throw mcpError('access-or-upstream-failure', `normalizePath: ${(e ).message}`);
361
+ }
358
362
  const [
359
363
  { runId: runIdA, appModel: appModelA, catalog: catalogA },
360
364
  { runId: runIdB, appModel: appModelB, catalog: catalogB },
@@ -364,26 +368,20 @@ export async function compareRuns(
364
368
  // interactive stage-detail drill-down, which none of compare/matchStages/metricDeltas/findingsDelta
365
369
  // read. Findings/metrics come from `catalog` and appModel.stages/sql, both fully populated. Produces
366
370
  // identical output to the dashboard; the MCP tool just never exposes per-task drill-down.
367
- const built = buildComparison(
371
+ const { comparison, output } = buildComparisonOutput(
368
372
  { label: runIdA, appModel: appModelA, catalog: catalogA },
369
373
  { label: runIdB, appModel: appModelB, catalog: catalogB },
374
+ { redact: opts?.redact, normalizePath: opts?.normalizePath, view: opts?.include?.includes('stagePairs') ? 'full' : 'summary' },
370
375
  );
371
- // Stage names throughout `built` carry raw Spark stage text, which can embed a host/IP token as
372
- // free text, the same residual redactReport() already scrubs from the evidence report.
373
- const result = opts?.redact ? redactComparison(built) : built;
374
- const verdict = comparisonVerdict(result);
375
376
 
376
377
  return {
377
378
  runIdA,
378
379
  runIdB,
379
- verdict,
380
- findingsDelta: result.findings,
381
- metricDeltas: result.metrics,
382
- confidence: result.confidence,
383
- reason: result.reason,
384
- matchedCoverage: result.matchedCoverage,
380
+ ...output,
381
+ findingsDelta: output.findings,
382
+ metricDeltas: output.metrics,
385
383
  ...tunedField(opts?.thresholds),
386
- ...(opts?.markdown ? { markdown: renderComparisonMarkdown(result, verdict, tunedDetectors(opts?.thresholds)) } : {}),
384
+ ...(opts?.markdown ? { markdown: renderComparisonMarkdown(comparison, output.verdict, tunedDetectors(opts?.thresholds)) } : {}),
387
385
  };
388
386
  }
389
387
 
@@ -408,6 +406,16 @@ export async function evaluateBudgetsForRun(
408
406
  if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
409
407
  throw mcpError('access-or-upstream-failure', 'regressionMetric requires maxRegressionPct.');
410
408
  }
409
+ // The CLI's rule: a metric is budgeted once across the single pair and the list.
410
+ try {
411
+ const pair = budgets.maxRegressionPct === undefined ? [] : [{ metric: budgets.regressionMetric ?? 'wallClock', maxPct: budgets.maxRegressionPct }];
412
+ combineRegressionBudgets([
413
+ ...pair.map((budget) => ({ origin: 'maxRegressionPct/regressionMetric', budget })),
414
+ ...(budgets.regressionBudgets ?? []).map((budget) => ({ origin: 'regressionBudgets', budget })),
415
+ ]);
416
+ } catch (e) {
417
+ throw mcpError('access-or-upstream-failure', (e ).message);
418
+ }
411
419
  const [first, second] = await Promise.all([
412
420
  resolveAndAnalyze(primary, opts?.thresholds),
413
421
  secondary ? resolveAndAnalyze(secondary, opts?.thresholds) : Promise.resolve(undefined),
@@ -418,11 +426,12 @@ export async function evaluateBudgetsForRun(
418
426
  // apply to the candidate.
419
427
  const candidate = second ?? first;
420
428
  const baseline = second ? first : undefined;
429
+ // Not redacted: no budget reads the stage text redaction rewrites (the parity suite checks it).
421
430
  const comparison = baseline
422
- ? buildComparison(
431
+ ? buildComparisonOutput(
423
432
  { label: baseline.runId, appModel: baseline.appModel, catalog: baseline.catalog },
424
433
  { label: candidate.runId, appModel: candidate.appModel, catalog: candidate.catalog },
425
- )
434
+ ).comparison
426
435
  : undefined;
427
436
 
428
437
  const { results, violated, inconclusive } = evaluateBudgets({
@@ -2,6 +2,7 @@
2
2
 
3
3
 
4
4
 
5
+
5
6
 
6
7
 
7
8
 
@@ -9,6 +10,15 @@
9
10
 
10
11
 
11
12
 
13
+ // The done message's counts of what the parse could not read. On the model, not only passed to onDone,
14
+ // so every consumer of the model (dashboard export, CLI, MCP) reports them the same way.
15
+ function recordParseGaps(appModel , data ) {
16
+ const done = data ;
17
+ appModel.skippedLines = done?.skippedLines ?? 0;
18
+ if (done?.unreadableSqlExecutions) appModel.unreadableSqlExecutions = done.unreadableSqlExecutions;
19
+ else delete appModel.unreadableSqlExecutions;
20
+ }
21
+
12
22
  // Worker-message -> appModel assembly. Shared by the file-load and SHS-URL-load paths. Pure model
13
23
  // mutation: analysis/render/persist stay in the caller via the onDone/onProgress/onError hooks.
14
24
  export function createModelCallbacks(
@@ -75,6 +85,18 @@ export function createModelCallbacks(
75
85
  }
76
86
  }
77
87
  },
78
- onDone, onError,
88
+ onDone(data ) {
89
+ recordParseGaps(appModel, data);
90
+ onDone?.(data);
91
+ },
92
+ // Patch the late work of failed attempts, whose TaskEnds arrive after StageCompleted.
93
+ // `data` is Map<stageId, StageAttemptTotals>.
94
+ onStageLateAttemptWork(data ) {
95
+ for (const [stageId, work] of data ) {
96
+ const stage = appModel.stages.get(stageId);
97
+ if (stage) stage.lateAttemptWork = work;
98
+ }
99
+ },
100
+ onError,
79
101
  };
80
102
  }
@@ -45,7 +45,7 @@ const NOT_AN_EVENT_LOG = 'Not a Spark event log: no application-start event foun
45
45
  // Minimal shape streamFile/runParse/runParseFiles read off `file` (name, size,
46
46
  // slice(start,end).arrayBuffer()): narrower than the full DOM `File`. A real
47
47
  // `File` (the browser Worker path) satisfies it structurally, but so does the
48
- // plain object src/cli/collect-run.ts's nodeFileFromPath builds for Node, which
48
+ // plain object packages/core/src/cli/collect-run.ts's nodeFileFromPath builds for Node, which
49
49
  // has no DOM `File` constructor.
50
50
 
51
51
 
@@ -1,6 +1,6 @@
1
1
  // Flattens a resolved planTree into a { nodes, edges } graph shape for the
2
2
  // React Flow + Dagre plan graph view ("Plan graph view" in
3
- // docs-site/contributor-guide/architecture/drill-down.md). The Exchange
3
+ // docs-site/contributor-guide/architecture/drill-down/plan-graph.md). The Exchange
4
4
  // write/read split is done upstream now, in resolvePlanTree
5
5
  // (event-handlers.ts): this file just walks the already-split tree and maps
6
6
  // each real PlanNode onto a PlanGraphNodeData. sourceNodeId still lets a
@@ -44,6 +44,17 @@ function pushLongFilterWarning(result , len ) {
44
44
  result.warnings.push({ type: 'longFilterCondition', detail: `Filter condition is ${len} characters: consider simplifying or pushing the filter down closer to the scan.` });
45
45
  }
46
46
 
47
+ // True when a node reads internal Delta metadata: the _delta_log state scan or an anonymous
48
+ // commit/checkpoint read. `Location:` is often truncated to `Del...`, so it also keys off
49
+ // DeltaLogFileIndex or the Delta-log action column signature. (Column-signature heuristic: a
50
+ // future Spark renaming those action columns would leak a noise row; acceptable.) Shared by
51
+ // `scanRelationId` and the comparison's Delta bookkeeping flag (stage-alignment.ts).
52
+ export function isDeltaLogRead(name , detail ) {
53
+ return /_delta_log/.test(name) || /_delta_log/.test(detail) ||
54
+ /DeltaLogFileIndex/i.test(name + detail) ||
55
+ /checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail);
56
+ }
57
+
47
58
  // Stable relation-identity key for a scan node: "<format>:<relation>" (e.g.
48
59
  // "delta:mx.store_map", "parquet:warehouse.sales", "jdbc:dw.dim_product")
49
60
  // or null when the node is internal Delta metadata / a non-scan / un-nameable.
@@ -70,16 +81,8 @@ export function scanRelationId(name , detail ) {
70
81
  return `${format}:${table.replace(/^spark_catalog\./, '')}`;
71
82
  }
72
83
 
73
- // 2. Internal Delta metadata (the _delta_log state scan and anonymous
74
- // commit/checkpoint reads) → null. Location: is often truncated to `Del...`,
75
- // so also key off DeltaLogFileIndex or the Delta-log action column signature.
76
- // (Column-signature heuristic: a future Spark renaming those action columns
77
- // would leak a noise row; acceptable.)
78
- if (/_delta_log/.test(name) || /_delta_log/.test(detail) ||
79
- /DeltaLogFileIndex/i.test(name + detail) ||
80
- /checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail)) {
81
- return null;
82
- }
84
+ // 2. Internal Delta metadata (see isDeltaLogRead) → null.
85
+ if (isDeltaLogRead(name, detail)) return null;
83
86
 
84
87
  // 3. Anonymous real file read (InMemoryFileIndex, no catalog name) →
85
88
  // best-effort Location basename. Preserves temp/snapshot self-reuse; a
@@ -10,7 +10,9 @@ function sendSafeError(res, status, code) {
10
10
  res.end(body);
11
11
  }
12
12
 
13
- function isConnectionFailure(error) {
13
+ // Also read by list-runs.ts, so a History Server listing that can't be reached reports the
14
+ // same upstream-unreachable code as a run fetch.
15
+ export function isConnectionFailure(error) {
14
16
  if (error?.name === 'AbortError' || error?.name === 'TimeoutError') return true;
15
17
  const code = error?.code ?? error?.cause?.code;
16
18
  if (['ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH', 'ENETUNREACH', 'ENOTFOUND', 'ETIMEDOUT'].includes(code)) return true;
@@ -0,0 +1,25 @@
1
+ import { planNodesOfStage } from './stage-plan-nodes.js';
2
+
3
+
4
+ // Plan operators that hand rows to a Python worker process: the row-at-a-time and Arrow Python UDF
5
+ // evaluators, the pandas/Arrow grouped and map operators, and the PythonRDD scan of an RDD
6
+ // pipeline. A suffixed name is a variant of the same operator (BatchEvalPythonUDTF,
7
+ // FlatMapGroupsInPandasWithState). Spark 4.1 renamed AggregateInPandas and WindowInPandas to
8
+ // ArrowAggregatePython and ArrowWindowPython. Spark prefixes a whole-stage-codegen child's name
9
+ // with "*(n) " in some plan strings.
10
+ const PYTHON_PLAN_NODE = /^(?:\*\(\d+\)\s*)?(?:PythonRDD|BatchEvalPython|ArrowEvalPython|ArrowAggregatePython|ArrowWindowPython|\w+InPandas|\w+InArrow)\w*\b/;
11
+
12
+ // An RDD lambda or map function has no SQL plan to match, and a stage whose plan could not be
13
+ // matched is left with nothing but its name and call site.
14
+ const PYTHON_STAGE_NAME = /PythonRDD/;
15
+ const PYTHON_STAGE_DETAILS = /org\.apache\.spark\.api\.python\./;
16
+
17
+ /** True when the stage ran Python code in a worker process: the union of a Python operator among
18
+ * the plan nodes attributed to it (catches Python UDFs inside SQL) and the stage's own name or
19
+ * call site naming PythonRDD / org.apache.spark.api.python (catches RDD lambdas, which have no
20
+ * plan, and stages that cannot be matched to one). The executor CPU time of such a stage misses
21
+ * the worker process's CPU. */
22
+ export function isPythonStage(stage , sql ) {
23
+ if (PYTHON_STAGE_NAME.test(stage.name ?? '') || PYTHON_STAGE_DETAILS.test(stage.details ?? '')) return true;
24
+ return planNodesOfStage(stage, sql).some((node) => PYTHON_PLAN_NODE.test(node.name ?? ''));
25
+ }
@@ -1,7 +1,7 @@
1
1
  // Explicit .ts extensions: plain Node's ESM resolver (the runtime CLI/MCP path
2
2
  // runs under) requires the exact specifier, unlike a bundler.
3
3
  import { mergeIntervals } from './intervals.js';
4
- import { formatWallClockRange, worstImpactBand, IMPACT_BAND_ORDER } from './format-utils.js';
4
+ import { formatRawWaste, formatWallClockRange, readsAsZero, worstImpactBand, IMPACT_BAND_ORDER } from './format-utils.js';
5
5
 
6
6
 
7
7
 
@@ -124,11 +124,15 @@ export function buildRecommendationRollup(
124
124
  });
125
125
  }
126
126
 
127
- /** A group's trailing figure on the Findings board, and its tooltip. `time`
128
- * and `resource` both lead with the "×N" finding count (the "worth
129
- * expanding" signal); `count` skips it since the impact-band tally already
130
- * implies N. The row stays terse ("×2 · 476ms recoverable") to fit a dense
131
- * right-aligned column; the title spells the shorthand out. */
127
+ /** A resource group's summed waste as the board and CLI print it; null when it reads as zero. */
128
+ export function resourceGroupTotal(group ) {
129
+ const text = formatRawWaste({ value: group.total, unit: group.unit });
130
+ return readsAsZero(text) ? null : text;
131
+ }
132
+
133
+ /** A group's trailing figure on the Findings board, and its tooltip. Every kind leads with the
134
+ * "×N" finding count (the "worth expanding" signal). The row stays terse ("×2 · 476ms
135
+ * recoverable") to fit a dense right-aligned column; the title spells the shorthand out. */
132
136
  export function rollupGroupStat(group ) {
133
137
  if (group.kind === 'time') {
134
138
  const recoverable = formatWallClockRange(group.recoverableMsHigh, group.recoverableMsHigh);
@@ -138,13 +142,16 @@ export function rollupGroupStat(group )
138
142
  };
139
143
  }
140
144
  if (group.kind === 'resource') {
145
+ const total = resourceGroupTotal(group);
141
146
  return {
142
- stat: `×${group.findingCount} · resource-cost projection`,
143
- statTitle: `${group.findingCount} findings of this type; a resource-cost estimate (not run time) is projected for fixing them`,
147
+ stat: total ? `×${group.findingCount} · ${total}` : `×${group.findingCount}`,
148
+ statTitle: `${group.findingCount} findings of this type${total ? `; ${total} in total, a resource cost, not run time` : ''}`,
144
149
  };
145
150
  }
151
+ // The band heading above the row already names a one-band group's band.
152
+ const bands = Object.entries(group.byImpactBand);
146
153
  return {
147
- stat: Object.entries(group.byImpactBand).map(([impactBand, count]) => `${count} ${impactBand}`).join(', '),
154
+ stat: bands.length === 1 ? `×${group.findingCount}` : `×${group.findingCount} · ${bands.map(([impactBand, count]) => `${count} ${impactBand}`).join(', ')}`,
148
155
  statTitle: `${group.findingCount} findings of this type, by impact`,
149
156
  };
150
157
  }
@@ -9,13 +9,12 @@
9
9
 
10
10
  import { decodeCollections, encodeCollections, } from './export-data.js';
11
11
 
12
- import { redactTaskFailureGroup, } from './task-failure.js';
12
+ import { REDACTED_TEXT, redactTaskFailureGroup, } from './task-failure.js';
13
13
 
14
14
  // Host / IP identifier patterns. Used to enumerate host names that surface only
15
- // inside free text: recommendation strings, `stageFailed`'s failure-reason
16
- // value, SQL relation/node names, never as a structured `host` field, so
17
- // redaction reaches those residuals too. Pseudonyms (`host-1`) match neither
18
- // pattern, keeping the scan idempotent.
15
+ // inside free text: recommendation strings, SQL relation/node names, never as a
16
+ // structured `host` field, so redaction reaches those residuals too. Pseudonyms
17
+ // (`host-1`) match neither pattern, keeping the scan idempotent.
19
18
  const HOST_PATTERNS = [
20
19
  // EC2-style ip-10-1-2-3 with an optional dotted domain (ip-10-1-2-3.ec2.internal).
21
20
  // Each domain label must start with an alphanumeric, so a trailing sentence
@@ -95,6 +94,31 @@ function redactFailureGroups (node ) {
95
94
  return node;
96
95
  }
97
96
 
97
+ // A stage's failure reason is Spark's free-text message and can carry file paths and data values
98
+ // too, so it is replaced outright, like a failure group's message: a `stageFailed` finding's
99
+ // `valueText`, the `stageFailureReason` of a stage record, a job's `exception` (the run outcome's
100
+ // fallback reason), and the report's `failureReason` copy. Walking by key name, like
101
+ // collectHostFields, needs no path list. Returns a fresh tree.
102
+ const REASON_KEYS = new Set(['stageFailureReason', 'exception', 'failureReason']);
103
+ function redactStageFailureReasons (node ) {
104
+ if (Array.isArray(node)) return node.map((n) => redactStageFailureReasons(n)) ;
105
+ if (node && typeof node === 'object') {
106
+ const isStageFailed = (node ).type === 'stageFailed';
107
+ const out = {};
108
+ for (const [k, v] of Object.entries(node)) {
109
+ const isReason = REASON_KEYS.has(k) || (isStageFailed && k === 'valueText');
110
+ out[k] = isReason && typeof v === 'string' ? REDACTED_TEXT : redactStageFailureReasons(v);
111
+ }
112
+ return out ;
113
+ }
114
+ return node;
115
+ }
116
+
117
+ // Both passes for free text no host/app-id pattern recognizes.
118
+ function redactFailureText (node ) {
119
+ return redactStageFailureReasons(redactFailureGroups(node));
120
+ }
121
+
98
122
  // Spark config keys ending in `host`/`hostname` (e.g. spark.driver.host,
99
123
  // spark.yarn.am.hostname) carry plain FQDN host names that neither
100
124
  // HOST_PATTERNS matches (no IP/EC2 shape) nor collectHostFields's by-key-name
@@ -122,7 +146,7 @@ function collectConfigHostValues(config , hos
122
146
 
123
147
 
124
148
 
125
-
149
+
126
150
 
127
151
 
128
152
 
@@ -188,10 +212,19 @@ function applyReplacements (node , ids
188
212
  return deepReplace(node, merged) ;
189
213
  }
190
214
 
215
+ // The app name identifies a job as much as its id, so a redacted name becomes the id's
216
+ // pseudonym, as list_runs does. Only the structured field is replaced, never substrings: a
217
+ // short name ("t", "etl") would otherwise corrupt every string it happens to occur in.
218
+ function withRedactedName (app ) {
219
+ return app.name == null ? app : { ...app, name: app.id ?? null };
220
+ }
221
+
191
222
  export function redactReport (input ) {
192
- const report = redactFailureGroups(input);
223
+ const report = redactFailureText(input);
193
224
  const { appIds, hosts } = collectIds(report);
194
- return applyReplacements(report, { appIds, hosts });
225
+ const out = applyReplacements(report, { appIds, hosts });
226
+ const app = out.summary?.app;
227
+ return app ? { ...out, summary: { ...out.summary, app: withRedactedName(app) } } : out;
195
228
  }
196
229
 
197
230
  // Run-comparison counterpart: no single app-id *field* to pseudonymize
@@ -218,7 +251,7 @@ export function redactComparison (comparison ) {
218
251
 
219
252
 
220
253
  function redactRunTree (input ) {
221
- const data = redactFailureGroups(input);
254
+ const data = redactFailureText(input);
222
255
  const appIds = new Set ();
223
256
  const hosts = new Set ();
224
257
  const appId = data.app?.id;
@@ -229,7 +262,12 @@ function redactRunTree (input ) {
229
262
  collectHostFields(data.configFindings, hosts);
230
263
  collectConfigHostValues(data.app?.config, hosts);
231
264
  scanTokens(data, [{ patterns: HOST_PATTERNS, out: hosts }, { patterns: APP_ID_PATTERNS, out: appIds }]);
232
- return applyReplacements(data, { appIds, hosts });
265
+ const out = applyReplacements(data, { appIds, hosts });
266
+ if (!out.app) return out;
267
+ const app = withRedactedName(out.app);
268
+ // spark.app.name repeats the name in the config the HTML export ships.
269
+ if (app.config?.['spark.app.name'] == null) return { ...out, app };
270
+ return { ...out, app: { ...app, config: { ...app.config, 'spark.app.name': app.id ?? '' } } };
233
271
  }
234
272
 
235
273
  /** A run's model and findings with every identifier pseudonymized. Redact this before anything derives
@@ -264,6 +302,9 @@ export function redactRunModel(
264
302
  executors: redacted.executors,
265
303
  runAggregates: redacted.runAggregates ,
266
304
  evidenceAvailability: redacted.evidenceAvailability ,
305
+ // Counts and execution ids, nothing to pseudonymize.
306
+ skippedLines: appModel.skippedLines,
307
+ unreadableSqlExecutions: appModel.unreadableSqlExecutions,
267
308
  } ,
268
309
  catalog: redacted.catalog,
269
310
  configFindings: redacted.configFindings,