sparkforensics-server 0.6.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/index.js +16 -7
  2. package/package.json +1 -1
  3. package/public/assets/AutoscalingChurn-DenWKTv-.js +1 -0
  4. package/public/assets/CacheUtilization-DLz7NdjV.js +1 -0
  5. package/public/assets/CachingOpportunity-zpQIqhzm.js +1 -0
  6. package/public/assets/ChartTheme-DAiARbLP.js +8 -0
  7. package/public/assets/ColdStart-C-9HjxWl.js +1 -0
  8. package/public/assets/ConfigAudit-D0V9tcrJ.js +1 -0
  9. package/public/assets/CoreUsageArea-yeerrljd.js +1 -0
  10. package/public/assets/Dashboard-DkUPrHKV.js +1 -0
  11. package/public/assets/DisclosureContext-DK1_iz6J.js +1 -0
  12. package/public/assets/DuplicatePlanSubtree-xDNDbCd3.js +1 -0
  13. package/public/assets/DurationHistogram-B6jsE0Ov.js +1 -0
  14. package/public/assets/EmptyState-C1uL40VU.js +1 -0
  15. package/public/assets/ExecutorUtilization-DXDZKmSc.js +1 -0
  16. package/public/assets/GcPressure-MRf7I4IG.js +1 -0
  17. package/public/assets/ImpactEstimate-Dm3xWbzV.js +1 -0
  18. package/public/assets/IncompleteRun-CaLOhMtf.js +1 -0
  19. package/public/assets/JobFailures-CzmzlvhO.js +1 -0
  20. package/public/assets/MemoryUtilization-AWUMUjCH.js +1 -0
  21. package/public/assets/NestedLoopJoin-C9sNr6Ss.js +1 -0
  22. package/public/assets/OverBroadcast-BOmRE6ca.js +1 -0
  23. package/public/assets/PartitionHint-BL2EmJfE.js +1 -0
  24. package/public/assets/PartitionSizing-BzBySvUq.js +1 -0
  25. package/public/assets/PlanExplorer-DdiQRcJU.js +1 -0
  26. package/public/assets/PlanGraphRoute-CIH9gBuR.js +1 -0
  27. package/public/assets/PlanView-B1hxzFRs.js +2 -0
  28. package/public/assets/PythonUdf-DvgCCueD.js +1 -0
  29. package/public/assets/RetryWaste-uXXu_CyM.js +1 -0
  30. package/public/assets/RowPagination-C-LzWkI5.js +1 -0
  31. package/public/assets/RowStatusCluster-Dbtmte7l.js +1 -0
  32. package/public/assets/ShuffleIO-nALwzC1_.js +1 -0
  33. package/public/assets/Skew-CHMEKgvk.js +1 -0
  34. package/public/assets/SlowHost-yyvjpHk6.js +1 -0
  35. package/public/assets/SmallFiles-y6odJ97n.js +1 -0
  36. package/public/assets/SpeculationWaste-C-SD2LOt.js +1 -0
  37. package/public/assets/Spill-DHTMmOup.js +1 -0
  38. package/public/assets/StageFailed-CQ3DxC9R.js +1 -0
  39. package/public/assets/StageFindingGroup-BgYLyj5q.js +1 -0
  40. package/public/assets/StageHeader-CIETkjVZ.js +1 -0
  41. package/public/assets/StagePill--P7PDd3s.js +1 -0
  42. package/public/assets/StageShape-BIv8vx_o.js +1 -0
  43. package/public/assets/StageSlowness-MMqzERxN.js +1 -0
  44. package/public/assets/Straggler-BgrY7AyW.js +1 -0
  45. package/public/assets/TaskFailures-BHHyKYTs.js +1 -0
  46. package/public/assets/TinyTask-DDScNhCl.js +1 -0
  47. package/public/assets/UnderBroadcast-CGIC04TI.js +1 -0
  48. package/public/assets/WidgetLeadSummary-CXFZP00C.js +1 -0
  49. package/public/assets/button-DbpTz2yk.js +1 -0
  50. package/public/assets/charts-vendor-CtTxwTeu.js +59 -0
  51. package/public/assets/composite-C9Nu2EI9.js +1 -0
  52. package/public/assets/d3-vendor-DNlD2-cz.js +1 -0
  53. package/public/assets/detector-registry-Bp8hrLua.js +2 -0
  54. package/public/assets/downsample-WmNM1qc9.js +1 -0
  55. package/public/assets/finding-anchor-Dr40LkS9.js +1 -0
  56. package/public/assets/findings-of-type-BL2u3RGZ.js +1 -0
  57. package/public/assets/impact-sort-mC5ISgX8.js +1 -0
  58. package/public/assets/inbox-KPU1Gppm.js +1 -0
  59. package/public/assets/index-CaRVPvAT.css +2 -0
  60. package/public/assets/index-fDiwfuoO.js +14 -0
  61. package/public/assets/inertValue-L9VxV0VE.js +1 -0
  62. package/public/assets/layout.worker-CLcBHxVp.js +3 -0
  63. package/public/assets/{parser-worker-CA5RxqKc.js → parser-worker-CRIOPMOW.js} +8 -8
  64. package/public/assets/plan-graph-vendor-Ce6d4HU6.js +7 -0
  65. package/public/assets/react-vendor-C6KcUD36.js +9 -0
  66. package/public/assets/select-BHqOqun5.js +1 -0
  67. package/public/assets/toggle-DPeN6aNa.js +1 -0
  68. package/public/assets/toggle-group-Kl3uyHSr.js +1 -0
  69. package/public/assets/usePagedRows-BafD6bEN.js +1 -0
  70. package/public/assets/useRecentFiles-DzJZAoOT.js +3 -0
  71. package/public/assets/useRegisterFieldControl-pQv-ZDoU.js +1 -0
  72. package/public/assets/useScrollLock-BCxn_dMp.js +1 -0
  73. package/public/assets/useSortMode-DjrAN-z4.js +1 -0
  74. package/public/docs/404.html +3 -3
  75. package/public/docs/assets/{app.CnWL6bKV.js → app.1gvzd5Zb.js} +1 -1
  76. package/public/docs/assets/chunks/@localSearchIndexroot.DS2TQP5_.js +1 -0
  77. package/public/docs/assets/chunks/{VPLocalSearchBox.C0IBKeRQ.js → VPLocalSearchBox.CNxT3s0I.js} +1 -1
  78. package/public/docs/assets/chunks/{theme.BH-2HuRf.js → theme.C5lLqUv7.js} +2 -2
  79. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.lean.js +1 -0
  81. package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.js} +1 -1
  82. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.js +1 -0
  83. package/public/docs/assets/{contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.js → contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.js} +1 -1
  84. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CHHgywoP.js +1 -0
  86. package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.N4hhenrA.lean.js → contributor-guide_architecture_detector-contract.md.CHHgywoP.lean.js} +1 -1
  87. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.js +1 -0
  90. package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.lean.js} +1 -1
  91. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.js} +1 -1
  92. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.lean.js} +1 -1
  93. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.js +1 -0
  94. package/public/docs/assets/{contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js → contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.lean.js} +1 -1
  95. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.K71YB-Rr.js +1 -0
  96. package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.DfvbqEUu.lean.js → contributor-guide_architecture_impact-estimation.md.K71YB-Rr.lean.js} +1 -1
  97. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.js +1 -0
  98. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.lean.js} +1 -1
  99. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_overview.md.nxkpOhgf.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.js +1 -0
  103. package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.lean.js} +1 -1
  104. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.lean.js +1 -0
  107. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.js} +1 -1
  108. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.js} +1 -1
  109. package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js → contributor-guide_architecture_worker-protocol.md.Br6OE7RW.js} +1 -1
  110. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.Br6OE7RW.lean.js +1 -0
  111. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.js +1 -0
  112. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.lean.js} +1 -1
  113. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.js} +1 -1
  114. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.js} +2 -2
  115. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.lean.js} +1 -1
  116. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.js +1 -0
  117. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.lean.js} +1 -1
  118. package/public/docs/assets/contributor-guide_contributing.md.DBW4drtZ.js +1 -0
  119. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.js +15 -0
  120. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.lean.js +1 -0
  121. package/public/docs/assets/contributor-guide_testing.md.DpkozNoG.js +4 -0
  122. package/public/docs/assets/{contributor-guide_testing.md.By6XGR9a.lean.js → contributor-guide_testing.md.DpkozNoG.lean.js} +1 -1
  123. package/public/docs/assets/{index.md.CHJVslga.js → index.md.lMm7SUMz.js} +1 -1
  124. package/public/docs/assets/{index.md.CHJVslga.lean.js → index.md.lMm7SUMz.lean.js} +1 -1
  125. package/public/docs/assets/{style.CyK9Ss73.css → style.Cfxqlj0y.css} +1 -1
  126. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.js +29 -0
  127. package/public/docs/assets/{user-guide_getting-started_ci-and-automation.md.BEepbDDL.lean.js → user-guide_getting-started_ci-and-automation.md.C0pcQoNc.lean.js} +1 -1
  128. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.js +1 -0
  129. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.js} +2 -2
  130. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.lean.js} +1 -1
  131. package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.js → user-guide_mcp-tools.md.49r3L2YE.js} +1 -1
  132. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js → user-guide_mcp-tools_compare-runs.md.BQ_kIptC.js} +11 -6
  133. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.BQ_kIptC.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.azCZXkH8.js +44 -0
  135. package/public/docs/assets/{user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js → user-guide_mcp-tools_diagnose-run.md.azCZXkH8.lean.js} +1 -1
  136. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.js} +2 -2
  137. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.lean.js} +1 -1
  138. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.js} +2 -1
  139. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.lean.js} +1 -1
  140. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.js +1 -0
  141. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.lean.js +1 -0
  142. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.js +1 -0
  143. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.lean.js +1 -0
  144. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -6
  145. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +6 -6
  146. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -6
  147. package/public/docs/contributor-guide/architecture/board-widgets.html +6 -6
  148. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +6 -6
  149. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -6
  150. package/public/docs/contributor-guide/architecture/detector-contract.html +6 -6
  151. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +6 -6
  152. package/public/docs/contributor-guide/architecture/drill-down.html +6 -6
  153. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -6
  154. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +6 -6
  155. package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -6
  156. package/public/docs/contributor-guide/architecture/index.html +5 -5
  157. package/public/docs/contributor-guide/architecture/overview.html +6 -6
  158. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +5 -5
  159. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -6
  160. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  161. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +6 -6
  162. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +5 -5
  163. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +6 -6
  164. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +6 -6
  166. package/public/docs/contributor-guide/architecture/widget-rendering.html +6 -6
  167. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -6
  168. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +6 -6
  169. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +7 -7
  170. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -6
  171. package/public/docs/contributor-guide/architecture/worker-protocol.html +6 -6
  172. package/public/docs/contributor-guide/contributing.html +6 -6
  173. package/public/docs/contributor-guide/development-setup.html +8 -8
  174. package/public/docs/contributor-guide/testing.html +9 -6
  175. package/public/docs/hashmap.json +1 -1
  176. package/public/docs/index.html +6 -6
  177. package/public/docs/logo-dark.svg +1 -0
  178. package/public/docs/logo-light.svg +1 -0
  179. package/public/docs/tuning-reference/anti-patterns.html +5 -5
  180. package/public/docs/tuning-reference/aqe.html +5 -5
  181. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -5
  182. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -5
  183. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -5
  184. package/public/docs/tuning-reference/bottleneck-failures.html +5 -5
  185. package/public/docs/tuning-reference/bottleneck-gc.html +5 -5
  186. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -5
  187. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -5
  188. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -5
  189. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -5
  190. package/public/docs/tuning-reference/bottleneck-skew.html +5 -5
  191. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -5
  192. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -5
  193. package/public/docs/tuning-reference/bottleneck-spill.html +5 -5
  194. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -5
  195. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -5
  196. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -5
  197. package/public/docs/tuning-reference/caching.html +5 -5
  198. package/public/docs/tuning-reference/cluster-config.html +5 -5
  199. package/public/docs/tuning-reference/config.html +5 -5
  200. package/public/docs/tuning-reference/data-formats.html +5 -5
  201. package/public/docs/tuning-reference/index.html +5 -5
  202. package/public/docs/tuning-reference/intro.html +5 -5
  203. package/public/docs/tuning-reference/joins.html +5 -5
  204. package/public/docs/tuning-reference/memory-model.html +5 -5
  205. package/public/docs/tuning-reference/metrics.html +5 -5
  206. package/public/docs/tuning-reference/partitioning.html +5 -5
  207. package/public/docs/tuning-reference/pyspark.html +5 -5
  208. package/public/docs/tuning-reference/shuffle.html +5 -5
  209. package/public/docs/tuning-reference/spark-architecture.html +5 -5
  210. package/public/docs/tuning-reference/table-formats.html +5 -5
  211. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +5 -5
  212. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +5 -5
  213. package/public/docs/user-guide/alternative-log-retrieval.html +5 -5
  214. package/public/docs/user-guide/getting-started/ci-and-automation.html +16 -11
  215. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +6 -6
  216. package/public/docs/user-guide/getting-started/regression-metric-keys.html +5 -5
  217. package/public/docs/user-guide/getting-started/tuning-thresholds.html +7 -7
  218. package/public/docs/user-guide/getting-started.html +5 -5
  219. package/public/docs/user-guide/mcp-tools/compare-runs.html +16 -11
  220. package/public/docs/user-guide/mcp-tools/diagnose-run.html +28 -7
  221. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -6
  222. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +5 -5
  223. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +7 -6
  224. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +5 -5
  225. package/public/docs/user-guide/mcp-tools/get-run-summary.html +5 -5
  226. package/public/docs/user-guide/mcp-tools/list-runs.html +5 -5
  227. package/public/docs/user-guide/mcp-tools.html +6 -6
  228. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +5 -5
  229. package/public/docs/user-guide/run-comparison.html +6 -6
  230. package/public/docs/user-guide/understanding-findings.html +6 -6
  231. package/public/export-template.html +45 -45
  232. package/public/index.html +8 -7
  233. package/public/sample-runs/sample-run.ndjson.gz +0 -0
  234. package/vendor-core/allocation.js +25 -23
  235. package/vendor-core/analyzer.js +85 -58
  236. package/vendor-core/api-merge-writes.js +2 -0
  237. package/vendor-core/aqe-skew.js +347 -0
  238. package/vendor-core/cli/budgets.js +6 -4
  239. package/vendor-core/cli/collect-run-pair.js +89 -0
  240. package/vendor-core/cli/collect-run-worker.js +16 -0
  241. package/vendor-core/cli/collect-run.js +1 -2
  242. package/vendor-core/cli/native-zstd.js +12 -16
  243. package/vendor-core/comparability.js +234 -0
  244. package/vendor-core/comparison-output.js +15 -7
  245. package/vendor-core/comparison-verdict.js +105 -25
  246. package/vendor-core/core-source-hash.txt +1 -1
  247. package/vendor-core/core-time-series.js +0 -1
  248. package/vendor-core/core-usage-locality.js +1 -1
  249. package/vendor-core/detectors.js +1007 -380
  250. package/vendor-core/docs-config.js +0 -4
  251. package/vendor-core/docs-content/detection/cache.md +2 -1
  252. package/vendor-core/docs-content/detection/cfg.md +19 -11
  253. package/vendor-core/docs-content/detection/cold.md +2 -1
  254. package/vendor-core/docs-content/detection/gc.md +7 -3
  255. package/vendor-core/docs-content/detection/host.md +11 -1
  256. package/vendor-core/docs-content/detection/mem.md +17 -6
  257. package/vendor-core/docs-content/detection/part.md +3 -1
  258. package/vendor-core/docs-content/detection/plan.md +55 -13
  259. package/vendor-core/docs-content/detection/shfl.md +11 -5
  260. package/vendor-core/docs-content/detection/skew.md +47 -1
  261. package/vendor-core/docs-content/detection/slow.md +4 -2
  262. package/vendor-core/docs-content/detection/spec.md +6 -1
  263. package/vendor-core/docs-content/detection/spill.md +6 -3
  264. package/vendor-core/docs-content/detection/strag.md +19 -6
  265. package/vendor-core/docs-content/detection/tiny.md +9 -3
  266. package/vendor-core/effective-conf.js +7 -10
  267. package/vendor-core/efficiency-model.js +2 -2
  268. package/vendor-core/etl-phases.js +1 -2
  269. package/vendor-core/event-handlers.js +175 -57
  270. package/vendor-core/event-schemas.js +21 -16
  271. package/vendor-core/evidence-availability.js +9 -2
  272. package/vendor-core/evidence-report.js +118 -35
  273. package/vendor-core/executor-peaks.js +41 -0
  274. package/vendor-core/export-data.js +5 -5
  275. package/vendor-core/finding-identity.js +78 -0
  276. package/vendor-core/finding-presentation.js +116 -33
  277. package/vendor-core/finding-types.js +88 -21
  278. package/vendor-core/format-utils.js +14 -15
  279. package/vendor-core/html-export.js +1 -1
  280. package/vendor-core/impact-band.js +2 -2
  281. package/vendor-core/impact-format.js +47 -19
  282. package/vendor-core/impact-model.js +26 -15
  283. package/vendor-core/job-groups.js +4 -4
  284. package/vendor-core/mcp-server-factory.js +48 -12
  285. package/vendor-core/mcp-tools.js +179 -24
  286. package/vendor-core/median.js +2 -2
  287. package/vendor-core/nested-loop-join.js +73 -0
  288. package/vendor-core/occupancy.js +1 -2
  289. package/vendor-core/parser-worker.js +1 -1
  290. package/vendor-core/plan-graph-model.js +14 -22
  291. package/vendor-core/plan-node-detail.js +2 -3
  292. package/vendor-core/plan-summary.js +24 -19
  293. package/vendor-core/plan-tree-walk.js +6 -8
  294. package/vendor-core/python-stage.js +12 -1
  295. package/vendor-core/recommendation-rollup.js +13 -12
  296. package/vendor-core/redact.js +52 -46
  297. package/vendor-core/run-aggregates.js +6 -8
  298. package/vendor-core/run-comparison.js +226 -66
  299. package/vendor-core/run-identity.js +12 -0
  300. package/vendor-core/run-interpretation.js +21 -0
  301. package/vendor-core/run-metrics.js +2 -2
  302. package/vendor-core/run-output.js +32 -10
  303. package/vendor-core/run-totals.js +12 -0
  304. package/vendor-core/run-verdict.js +76 -3
  305. package/vendor-core/scaling-sim.js +3 -4
  306. package/vendor-core/snappy-block.js +2 -2
  307. package/vendor-core/spark-conf.js +168 -0
  308. package/vendor-core/sql-stages.js +2 -2
  309. package/vendor-core/stage-alignment.js +2 -12
  310. package/vendor-core/stage-identity.js +58 -3
  311. package/vendor-core/stage-label.js +14 -0
  312. package/vendor-core/stage-plan-nodes.js +17 -3
  313. package/vendor-core/stage-quantiles.js +141 -9
  314. package/vendor-core/stage-structure.js +17 -3
  315. package/vendor-core/threshold-overrides.js +9 -3
  316. package/vendor-core/types.js +58 -21
  317. package/vendor-core/wasted-core-hours.js +0 -1
  318. package/vendor-core/write-targets.js +114 -14
  319. package/vendor-core/zstd-worker-client.js +1 -1
  320. package/vendor-core/zstd-worker.js +1 -1
  321. package/public/assets/AutoscalingChurn-CRA2Mb7F.js +0 -1
  322. package/public/assets/CacheUtilization-Bh9OY_V9.js +0 -1
  323. package/public/assets/CachingOpportunity-oCiQwmTJ.js +0 -1
  324. package/public/assets/ColdStart-BJrtjfmn.js +0 -1
  325. package/public/assets/ConfigAudit-DMvKxySP.js +0 -1
  326. package/public/assets/CoreUsageArea-REC2zsKw.js +0 -1
  327. package/public/assets/Dashboard-BHZ76JCu.js +0 -2
  328. package/public/assets/DisclosureContext-OO7X1HAx.js +0 -1
  329. package/public/assets/DuplicatePlanSubtree-BgG-L-cK.js +0 -1
  330. package/public/assets/ExecutorUtilization-D2KhSVbY.js +0 -1
  331. package/public/assets/GcPressure-D1BTE8qh.js +0 -1
  332. package/public/assets/IncompleteRun-DD40xUpy.js +0 -1
  333. package/public/assets/JobFailures-YwNzCF19.js +0 -1
  334. package/public/assets/MemoryUtilization-9b8ee_3_.js +0 -1
  335. package/public/assets/OverBroadcast-CfctS8a8.js +0 -1
  336. package/public/assets/PartitionHint-CUvbnnxy.js +0 -1
  337. package/public/assets/PartitionSizing-VG8kBgG5.js +0 -1
  338. package/public/assets/PlanExplorer-DmubTAOP.js +0 -1
  339. package/public/assets/PlanGraphRoute-DctPZjS0.js +0 -1
  340. package/public/assets/RetryWaste-CnpM8bB7.js +0 -1
  341. package/public/assets/ShuffleIO-D13vGmas.js +0 -1
  342. package/public/assets/Skew-DlagstYP.js +0 -1
  343. package/public/assets/SlowHost-cSkLRZ_e.js +0 -1
  344. package/public/assets/SmallFiles-B-kERMcq.js +0 -1
  345. package/public/assets/SpeculationWaste-DaysAzXO.js +0 -1
  346. package/public/assets/Spill-H44Gz4US.js +0 -1
  347. package/public/assets/StageFailed-DCfii9h_.js +0 -1
  348. package/public/assets/StageFindingGroup-BXJ0Mj5n.js +0 -1
  349. package/public/assets/StageHeader--oN2J-oD.js +0 -1
  350. package/public/assets/StageShape-IZ80CZiJ.js +0 -1
  351. package/public/assets/StageSlowness--BRIaBc1.js +0 -1
  352. package/public/assets/Straggler-Ctq0Df_o.js +0 -1
  353. package/public/assets/TaskFailures-Bks_gqhL.js +0 -1
  354. package/public/assets/TinyTask-BssfzGFB.js +0 -1
  355. package/public/assets/UnderBroadcast-DsScrxc-.js +0 -1
  356. package/public/assets/charts-vendor-0VMaLVLN.js +0 -59
  357. package/public/assets/finding-anchor-BYW1oz5R.js +0 -1
  358. package/public/assets/impact-sort-xzy0g-Vh.js +0 -1
  359. package/public/assets/index-DhpRGMZc.css +0 -2
  360. package/public/assets/index-DwqkFUFk.js +0 -14
  361. package/public/assets/plan-graph-vendor-BYl-AzN5.js +0 -7
  362. package/public/assets/react-vendor-DfpuPQZf.js +0 -9
  363. package/public/assets/select-C8edjYTq.js +0 -1
  364. package/public/assets/usePagedRows-CsuUFeYx.js +0 -1
  365. package/public/assets/useRecentFiles-BvkKo3JN.js +0 -11
  366. package/public/assets/useSortMode-joX71BZN.js +0 -1
  367. package/public/docs/assets/chunks/@localSearchIndexroot.tE5pz54z.js +0 -1
  368. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +0 -1
  369. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +0 -1
  370. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +0 -1
  371. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.lean.js +0 -1
  372. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.N4hhenrA.js +0 -1
  373. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +0 -1
  374. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +0 -1
  375. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.js +0 -1
  376. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +0 -1
  377. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.DfvbqEUu.js +0 -1
  378. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.js +0 -1
  379. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +0 -1
  380. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +0 -1
  381. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +0 -1
  382. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +0 -1
  383. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +0 -1
  384. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +0 -1
  385. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +0 -1
  386. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +0 -1
  387. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.js +0 -1
  388. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.js +0 -1
  389. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +0 -1
  390. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.js +0 -15
  391. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.lean.js +0 -1
  392. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +0 -1
  393. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.js +0 -24
  394. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +0 -1
  395. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +0 -1
  396. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +0 -23
  397. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +0 -1
  398. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +0 -1
  399. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.js +0 -1
  400. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.lean.js +0 -1
  401. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.lean.js} +0 -0
  402. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js → contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.lean.js} +0 -0
  403. /package/public/docs/assets/{contributor-guide_architecture_overview.md.C1sSvm9i.lean.js → contributor-guide_architecture_overview.md.nxkpOhgf.lean.js} +0 -0
  404. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js → contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.lean.js} +0 -0
  405. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.lean.js} +0 -0
  406. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.lean.js} +0 -0
  407. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.lean.js} +0 -0
  408. /package/public/docs/assets/{contributor-guide_contributing.md.CQMvGwFt.lean.js → contributor-guide_contributing.md.DBW4drtZ.lean.js} +0 -0
  409. /package/public/docs/assets/{user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js → user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.lean.js} +0 -0
  410. /package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.lean.js → user-guide_mcp-tools.md.49r3L2YE.lean.js} +0 -0
@@ -0,0 +1 @@
1
+ import{_ as o,o as d,c as t,a5 as c}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Evidence report and finding identity","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/architecture/worker-protocol/evidence-report.md","filePath":"contributor-guide/architecture/worker-protocol/evidence-report.md"}'),a={name:"contributor-guide/architecture/worker-protocol/evidence-report.md"};function n(i,e,s,r,l,h){return d(),t("div",null,[...e[0]||(e[0]=[c('<h1 id="evidence-report-and-finding-identity" tabindex="-1">Evidence report and finding identity <a class="header-anchor" href="#evidence-report-and-finding-identity" aria-label="Permalink to &quot;Evidence report and finding identity&quot;">​</a></h1><p>The portable evidence report and the stable identity every finding carries.</p><h2 id="portable-evidence-report" tabindex="-1">Portable evidence report <a class="header-anchor" href="#portable-evidence-report" aria-label="Permalink to &quot;Portable evidence report&quot;">​</a></h2><p><code>packages/core/src/evidence-report.ts</code>&#39;s <code>buildEvidenceReport(appModel, { redact })</code> returns <code>{ markdown, json }</code>: a self-contained, byte-stable document that runs the detectors over an <code>appModel</code> and serializes the result for sharing outside the tool. Raw task records are never included; identifier redaction (app id + host names → <code>app-1</code>/<code>host-1</code> pseudonyms via <code>packages/core/src/redact.ts</code>, with the app name and <code>spark.app.name</code> replaced by the app id&#39;s pseudonym) is opt-in with <code>{ redact: true }</code>. The JSON is pinned by <code>EVIDENCE_SCHEMA_VERSION</code> (<code>packages/core/src/evidence-report.ts</code>, surfaced as <code>json.schemaVersion</code>) and has this fixed top-level key order:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>schemaVersion, summary, verdict, evidenceAvailability, detectors, findings, writeTargets, recommendations, cleanChecks, notRunChecks</span></span></code></pre></div><ul><li><code>summary</code> is the run header: <code>{ app: { id, name, sparkVersion }, stageCount, jobCount, sqlExecutionCount, findingCount, impactBandCounts, actionableFindingCount, actionableImpactBandCounts, clean, outcome, runShape }</code>. <code>findingCount</code> counts every row in <code>findings</code>; the <code>actionable*</code> counts leave out evidence caveats and the <code>incompleteRun</code> row, the same set the dashboard&#39;s top bar and verdict count. <code>clean</code> is <code>isCleanRun</code> from <code>packages/core/src/check-coverage.ts</code>, and <code>outcome</code> is <code>{ failedJobs, totalJobs, failureReason, failureReasonStageId }</code> from <code>summarizeRunOutcome</code> (<code>packages/core/src/run-outcome.ts</code>), the job results the dashboard verdict leads with; the Markdown prints it as an <code>- Outcome:</code> header line. <code>runShape</code> (<code>computeRunShape</code>, <code>packages/core/src/run-shape.ts</code>) is the Scorecard&#39;s wall-clock, Efficiency and Unused core time, the ETL phases&#39; summed stage time and Core Usage by Locality&#39;s peak busy cores, each <code>null</code> where the dashboard shows none; MCP <code>get_run_summary</code> returns it too.</li><li><code>verdict</code> is the dashboard&#39;s run verdict from <code>buildRunVerdict</code> (<code>packages/core/src/run-verdict.ts</code>): <code>{ title, summary, steps, costSteps, idle, remainingPlaces, copyText }</code>. <code>steps</code> holds the first three places to look in the verdict card&#39;s order, each <code>{ key, stageId, type, tag, leadFindingId, actionLabel, recommendation, impact, impactMeaning, relatedTypes, text }</code>, where <code>text</code> is that step&#39;s line of the &quot;Copy next steps&quot; checklist and <code>copyText</code> is the whole checklist. <code>steps</code> rank by wall clock. <code>costSteps</code> is a second lane ranked by measured busy core time (<code>impactEstimate.coreTimeMs.high</code>), each <code>{ stageId, type, tag, leadFindingId, actionLabel, coreTimeMs, coreTime }</code>: a finding with no core-time figure (an idle capacity, modeled or bytes-only figure) is in neither lane. <code>idle</code> is the run&#39;s allocated core time that ran no task, <code>{ coreHours, allocatedCoreHours, pct, text }</code>, or <code>null</code> without an allocation; no single finding&#39;s idle figure exceeds it. The Markdown opens with it as <code>## Verdict</code>, the two lanes labeled &quot;By wall clock&quot; and &quot;By core time&quot;. The <code>recommendations</code> rollup stays alongside: it ranks fix types, the verdict ranks places.</li><li><code>evidenceAvailability</code> is the ledger above (or <code>null</code> when absent).</li><li><code>detectors</code> is <code>detectorCatalog()</code> output: one <code>{ type, version, scope, thresholds, docAnchor }</code> per detector, in <code>DETECTORS</code> order, so the exact threshold set that produced each finding travels with the evidence. On a tuned run (see the tuned-run paragraph below) a tuned row&#39;s <code>thresholds</code> are the ones the run used, plus <code>tunedThresholds</code>.</li><li><code>findings</code> are deterministically sorted rows (impact band → type → stage → id), each with an <code>id</code> (a hash of the finding, so it matches within one run), a <code>key</code> (<code>findingKeyer()</code> in <code>packages/core/src/run-comparison.ts</code>: type, rule and location, where a stage is its <code>stageIdentity</code> fingerprint, the key of <code>metrics.stages</code>, and an SQL execution is a digest of its stages&#39; fingerprints, so it matches across reruns of one job), <code>tag</code>, core columns, an always-present <code>actionLabel</code>, and an <code>evidence</code> sub-object holding that finding type&#39;s declared evidence fields (see the per-type evidence paragraph below); <code>confidence</code>/ <code>validationRequired</code>/<code>docAnchor</code> appear only when the detector emitted them.</li><li><code>writeTargets</code> is <code>extractWriteTargets</code> output (<code>packages/core/src/write-targets.ts</code>): every SQL write command with its path or table. The field contract is in the user guide&#39;s <a href="./../../../user-guide/getting-started/ci-and-automation.html#write-targets">Write targets</a>.</li><li><code>recommendations</code> is the impact-ranked <code>buildRecommendationRollup</code> output (<code>packages/core/src/recommendation-rollup.ts</code>), and <code>cleanChecks</code> lists every detector type that fired zero findings this run and could run: see the rollup and clean-checks paragraph below.</li><li><code>notRunChecks</code> lists the zero-finding types the log lacked the data to run, each with a <code>reason</code>: see the not-run checks paragraph below.</li></ul><p>Determinism holds because detector order, finding sort, and object key order are all fixed, so a given <code>appModel</code> serializes identically across calls.</p><p>Redaction enumerates hosts two ways: by walking the findings tree for every string value under a key literally named <code>host</code> (<code>evidence.host</code>, <code>evidence.failedTaskDetails[].host</code>, <code>evidence.retriedTaskDetails[].host</code>, and any nested <code>host</code> field, all covered without enumerating paths), and by scanning every string value for EC2-style hostnames / bare IPv4 tokens. So identifiers that surface only in free text (recommendation copy, a <code>stageFailed</code> failure reason in <code>valueText</code>) are pseudonymized too. Pseudonym numbering uses a numeric-aware sort, so re-redacting an already-redacted report is a no-op even past <code>host-10</code>.</p><p>Failed-task error text is dropped rather than pseudonymized, since a message or stack trace can carry file paths and data values that no host pattern matches: every array under a key named <code>failureGroups</code> has its <code>message</code> replaced and the message text stripped from its <code>stackExcerpt</code> (<code>redactTaskFailureGroup</code> in <code>task-failure.ts</code>). That covers the evidence report and both the findings and the stage records of the HTML export. A stage&#39;s failure reason is replaced outright with <code>[redacted]</code> wherever it appears: a <code>stageFailed</code> finding&#39;s <code>valueText</code>, a stage record&#39;s <code>stageFailureReason</code>, a job&#39;s <code>exception</code> and the report&#39;s <code>failureReason</code> (<code>redactStageFailureReasons</code> in <code>redact.ts</code>). An executor&#39;s removal reason (<code>executors.removed[].reason</code> in the HTML export&#39;s run model) is also replaced with <code>[redacted]</code>, by field and not through the failure-reason keys (<code>withoutExecutorRemovalReasons</code> in <code>redact.ts</code>). No surface displays it.</p><p>The Markdown rendering mirrors the JSON&#39;s field set: each finding block prints its <code>detector version</code>, its sorted <code>evidence</code> entries (byte-magnitude keys humanized), and the report ends with a <code>## Detectors</code> catalog carrying the version + threshold set. A finding&#39;s <code>impactEstimate</code> prints as its own <code>- impact: </code> line worded as the dashboard&#39;s &quot;Potential savings&quot;: the wall-clock range, or the raw waste only when there is no range, followed by what it counts (&quot;of run time&quot;, &quot;of unused executor memory&quot;, &quot;of task time (modeled)&quot;) and <code>estimateMethod</code>, and nothing for an informational or zero estimate. A <code>- estimate: </code> line carries the Advanced view&#39;s provenance sentence (<code>estimateProvenance</code>). Both come from <code>packages/core/src/impact-format.ts</code>, the formatters every dashboard surface uses, so memory reads in GB-h from 0.1 GB-h up, core time in core-s or core-h, and a time figure has no &quot;Estimated&quot; prefix. JSON finding rows carry the same figure as <code>impact</code>/<code>impactMeaning</code> when there is one, plus the provenance sentence as <code>estimateProvenance</code> (with the model&#39;s assumptions), and each <code>recommendations</code> row an <code>impactMeaning</code> next to its <code>impact</code>, which is null for a resource figure that rounds to zero. <code>EvidenceExport</code> names downloads <code>evidence-&lt;appId&gt;[-redacted].&lt;md|json|html&gt;</code>, taking the app id from the (already pseudonymized when redacting) report so a redacted file never leaks the real id and is never name-identical to a raw export.</p><p>A finding row carries <code>impactEstimate</code> when the finding has one, with the full contract documented in <a href="./../impact-estimation.html#occupancy-weighted-attribution">Impact estimation</a> (basis, wallClock, estimateMethod, rawWaste, coreTimeMs, and on <code>utilization</code> idleCoreTimeMs). It appears after the pinned core columns (<code>id</code>, <code>key</code>, <code>type</code>, <code>impactBand</code>, <code>stageId</code>, <code>metric</code>, <code>value</code>, <code>recommendation</code>, <code>detectorVersion</code>, plus optional but pinned <code>confidence</code>, <code>validationRequired</code>, <code>docAnchor</code>) without displacing any of them. The full row shape is <code>FindingRowColumns</code> in <code>evidence-report.ts</code>, which also carries <code>name</code>, <code>tag</code>, <code>valueText</code>, <code>remediation</code>, <code>actionLabel</code>, <code>impact</code>/<code>impactMeaning</code>, <code>estimateProvenance</code> and, on a tuned run, <code>tunedThresholds</code>.</p><p>The report carries the same recommendation data the dashboard&#39;s &quot;Fix These First&quot; view shows, so the CLI (<code>packages/cli/bin/sparkforensics-analyze.mjs</code>) and the MCP tool (<code>diagnoseRun</code> in <code>packages/core/src/mcp-tools.ts</code>) get it too, not just the web markdown/JSON download. <code>FindingRow.actionLabel</code> is always present (a short imperative label like &quot;Reduce shuffle size&quot;), from <code>findingActionLabel</code> (<code>packages/core/src/finding-action-label.ts</code>), which reads the type&#39;s <code>actionLabel</code> in <code>finding-presentation.ts</code> and falls back to the type&#39;s name. The dashboard, the run verdict and the report rows all call it. <code>EvidenceReportJson.recommendations</code> is the same impact-ranked <code>buildRecommendationRollup</code> grouping (<code>packages/core/src/recommendation-rollup.ts</code>) that <code>FixTheseFirst.tsx</code> renders, so CLI/MCP/download consumers get the same &quot;what&#39;s the highest-impact fix&quot; ranking the dashboard shows, without changing the <code>findings</code> array&#39;s own impact-band-sorted order at all. <code>EvidenceReportJson.cleanChecks</code> lists every detector type that fired zero findings this run, each with <code>getThresholdSummary</code>&#39;s one-line &quot;what would have tripped it&quot; sentence; unlike the dashboard&#39;s <code>Alerts.tsx</code> clean-checks table, it deliberately includes the one &quot;always-mounted&quot; reference type (<code>coreLocality</code>) even when it has no findings, since a flat evidence report has no separate always-visible surface for it to already appear on the way that widget does on the board.</p><p>A check the log could not run is not listed as clean. <code>packages/core/src/check-coverage.ts</code> holds the one rule, shared with the dashboard&#39;s verdict, top bar and Clean checks: a type whose only finding is an evidence caveat, every <code>scope: &#39;stage&#39;</code> type on a log where no stage recorded an end, and the run-span types (<code>utilization</code>, <code>memoryUtilization</code>, <code>autoscalingChurn</code>) on a log with no ApplicationEnd. Those types are listed in <code>notRunChecks</code> instead of <code>cleanChecks</code>, each <code>{ type, tag, thresholdSummary, reason }</code>, where <code>reason</code> is the caveat&#39;s own recommendation (it names the setting to turn on) or the log-wide sentence. The Markdown has a <code>## Not checked on this log</code> section above <code>## Clean checks</code>, and a <code>Findings to act on</code> header line.</p><p><code>Finding</code> is a union discriminated on <code>type</code>, one member per emitted finding type (<code>packages/core/src/finding-types.ts</code>), and a finding row&#39;s <code>evidence</code> is an explicit per-type projection. Each type&#39;s <code>&lt;Type&gt;Evidence</code> interface names its public fields, and <code>EVIDENCE_KEYS</code> in <code>evidence-report.ts</code> lists the same keys, checked both ways at compile time, so a field a detector adds only for another core module does not become report contract. Rows leave out these fields: <code>stageShape</code>&#39;s <code>totalCores</code>; <code>utilization</code>&#39;s <code>utilizationFraction</code>, <code>appDurationMs</code>, <code>totalCores</code> and <code>allocatedCoreMs</code>; <code>memoryUtilization</code>&#39;s <code>idleRateFraction</code>, <code>allocatedMBSeconds</code>, <code>allocatedMB</code>, <code>peakExecutors</code>, <code>appDurationMs</code> and <code>allocatedBytes</code>; <code>retryWaste</code>&#39;s <code>extended</code> display copy. The impact estimator reads them on the finding. <code>value</code> is always numeric or <code>null</code>: the text-valued findings (<code>stageFailed</code>&#39;s failure reason, <code>configAudit</code>&#39;s current setting, <code>incompleteRun</code>&#39;s <code>missing</code>) carry their text in a <code>valueText</code> column, present only on those rows, and the Markdown prints it where <code>value</code> would go. Renaming or removing an evidence field is a breaking change and needs a schema bump. <code>cleanChecks</code>/<code>notRunChecks</code> list emitted finding types, the set the dashboard&#39;s Clean checks shows: <code>overBroadcast</code> and <code>underBroadcast</code> in place of the <code>broadcastSizing</code> detector entry. A finding row&#39;s <code>actionLabel</code> for a (type, discriminant) combination with no label of its own is the type&#39;s name, the same fallback the verdict step uses.</p><p><code>buildEvidenceReport(appModel, { thresholds })</code> runs the detectors with a user&#39;s validated overrides (the CLI&#39;s and MCP server&#39;s <code>--thresholds</code>; see <a href="./../detector-contract.html#tuning-thresholds">Tuning thresholds</a>). Only a tuned run adds keys: <code>tunedThresholds</code> (<code>{ &lt;name&gt;: { value, default } }</code>) on each finding row and each <code>cleanChecks</code>/<code>notRunChecks</code> entry from a detector an override moved off its defaults (or whose <code>suppressedBy</code> or threshold-source detector it moved), on that detector&#39;s <code>detectors</code> row, and as <code>summary.tunedThresholds</code> keyed by detector type. The Markdown adds a <code>- Tuned thresholds:</code> header line, a <code>- tuned thresholds:</code> line per affected finding, and marks tuned catalog rows and clean checks. A default run&#39;s report carries none of these keys, and every one is optional. The report caches key on the overrides object as well as the <code>appModel</code>, so one model&#39;s default and tuned reports never mix. Finding ids ignore thresholds: a finding a tuned run still emits keeps the id it has in a default run.</p><h2 id="finding-identity" tabindex="-1">Finding identity <a class="header-anchor" href="#finding-identity" aria-label="Permalink to &quot;Finding identity&quot;">​</a></h2><p>Every finding <code>analyzer.ts</code> emits carries a stable <code>id</code> (<code>push()</code>&#39;s <code>findingId()</code> choke point, FNV-1a hash over <code>type | location | metric | value | discriminators</code>) and a <code>detectorVersion</code> (from the emitting <code>DETECTORS</code> entry). The two are deliberately decoupled: <code>id</code> is derived only from a finding&#39;s evidence tuple, never from <code>detectorVersion</code>, so a threshold or logic tweak that bumps a detector&#39;s <code>version</code> does not change the <code>id</code> of findings it still emits at the same location/metric. A saved reference (dashboard bookmark, exported report row) keeps pointing at the same logical finding across detector revisions; <code>detectorVersion</code> is separate provenance metadata for &quot;which ruleset produced this,&quot; not part of identity. See <code>packages/core/test/analyzer-finding-identity.test.js</code> for the decoupling proof.</p><p>The discriminators are declared per finding type (<code>ID_DISCRIMINATORS</code> in <code>analyzer.ts</code>, each key checked against that type&#39;s fields) and joined in the fixed <code>DISCRIMINATOR_SLOTS</code> order, and the value slot takes <code>value</code>, else <code>valueText</code>.</p><p><code>id</code> stability holds for equivalent reruns of the same schema/analyzer version on the same input. Changing the id-derivation rule itself (the hash algorithm, or which fields feed the location key/discriminators) is an intentional breaking change and must bump <code>EVIDENCE_SCHEMA_VERSION</code> (<code>packages/core/src/evidence-report.ts</code>); there is no separate id-scheme version.</p>',19)])])}const m=o(a,[["render",n]]);export{u as __pageData,m as default};
@@ -1 +1 @@
1
- import{_ as o,o as d,c as t,a5 as c}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Evidence report and finding identity","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/architecture/worker-protocol/evidence-report.md","filePath":"contributor-guide/architecture/worker-protocol/evidence-report.md"}'),a={name:"contributor-guide/architecture/worker-protocol/evidence-report.md"};function n(i,e,r,s,l,h){return d(),t("div",null,[...e[0]||(e[0]=[c("",19)])])}const m=o(a,[["render",n]]);export{u as __pageData,m as default};
1
+ import{_ as o,o as d,c as t,a5 as c}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Evidence report and finding identity","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/architecture/worker-protocol/evidence-report.md","filePath":"contributor-guide/architecture/worker-protocol/evidence-report.md"}'),a={name:"contributor-guide/architecture/worker-protocol/evidence-report.md"};function n(i,e,s,r,l,h){return d(),t("div",null,[...e[0]||(e[0]=[c("",19)])])}const m=o(a,[["render",n]]);export{u as __pageData,m as default};
@@ -0,0 +1 @@
1
+ import{_ as s,o,c as a,a5 as i}from"./chunks/framework.DSg0KOwT.js";const g=JSON.parse('{"title":"Contributing","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/contributing.md","filePath":"contributor-guide/contributing.md"}'),t={name:"contributor-guide/contributing.md"};function n(c,e,d,r,h,p){return o(),a("div",null,[...e[0]||(e[0]=[i('<h1 id="contributing" tabindex="-1">Contributing <a class="header-anchor" href="#contributing" aria-label="Permalink to &quot;Contributing&quot;">​</a></h1><h2 id="where-does-new-content-go" tabindex="-1">Where does new content go? <a class="header-anchor" href="#where-does-new-content-go" aria-label="Permalink to &quot;Where does new content go?&quot;">​</a></h2><ul><li><code>docs/</code> is for internal engineering records, such as the design and product notes in <code>docs/DESIGN.md</code> and <code>docs/PRODUCT.md</code>. It stays flat, unstructured markdown, read through GitHub&#39;s own file viewer. Working plans and specs (<code>docs/plans/</code>, <code>docs/specs/</code>, <code>docs/superpowers/</code>), like audits in <code>docs/audit/</code>, are gitignored: never commit them.</li><li><code>docs-site/</code> (this site) is the published documentation for users and contributors. If you&#39;re writing something a user or a first-time contributor should read, it belongs here, not in <code>docs/</code>.</li></ul><h2 id="linting-before-you-commit" tabindex="-1">Linting before you commit <a class="header-anchor" href="#linting-before-you-commit" aria-label="Permalink to &quot;Linting before you commit&quot;">​</a></h2><p><code>npm install</code> runs the <code>prepare</code> script, which points git at <code>.githooks/</code> (<code>git config core.hooksPath .githooks</code>). After that, every commit runs <code>.githooks/pre-commit</code>: it lints any staged <code>.js</code>/<code>.mjs</code> files with eslint and blocks the commit if linting fails. <code>git commit --no-verify</code> skips it. To run the same linter by hand, use <code>npm run lint</code>.</p><h2 id="changesets-and-releases" tabindex="-1">Changesets and releases <a class="header-anchor" href="#changesets-and-releases" aria-label="Permalink to &quot;Changesets and releases&quot;">​</a></h2><p>A changeset is required for a PR that touches <code>packages/core</code>, <code>packages/cli</code>, <code>packages/mcp</code>, <code>packages/server</code>, <code>packages/analyze</code>, <code>packages/sparkforensics</code>, or the root site app (<code>src/</code>, <code>index.html</code>, <code>vite.config.ts</code>, or the root <code>package.json</code>). Run <code>npx changeset add</code>, answer its prompts, and commit the generated <code>.changeset/*.md</code> file. CI&#39;s <code>changeset</code> job (<code>scripts/check-changeset.sh</code>) fails the PR otherwise, with instructions to fix it. <code>docs-site/</code> and <code>docs/</code> are exempt: doc-only changes don&#39;t need one.</p><p>Needing a changeset doesn&#39;t mean getting published, though. Only <code>packages/cli</code>, <code>packages/mcp</code>, <code>packages/server</code>, and the two alias packages publish to npm. The aliases, <code>packages/analyze</code> (<code>sparkforensics-analyze</code>) and <code>packages/sparkforensics</code> (<code>sparkforensics</code>), hold no code of their own: each depends on <code>sparkforensics-cli</code> and runs its <code>sparkforensics-analyze</code> bin, so <code>npx sparkforensics-analyze</code> works outside a checkout and nobody else can publish under either name. <code>.changeset/config.json</code>&#39;s <code>fixed</code> group keeps their versions equal to <code>sparkforensics-cli</code>&#39;s, so every CLI release republishes them. The root site (<code>sparkforensics-web</code>) is <code>&quot;private&quot;: true</code> and never published; its changeset only bumps its <code>package.json</code> version and writes a <code>CHANGELOG.md</code> entry, via <code>.changeset/config.json</code>&#39;s <code>privatePackages.version</code> setting. <code>@sparkforensics/core</code> is the other private package but stays out of this entirely (<code>.changeset/config.json</code>&#39;s <code>ignore</code> list): it&#39;s vendored into each publishable code package (<code>packages/{cli,mcp,server}</code>) by filesystem copy at pack time (<code>scripts/vendor-core.mjs</code>), not read as a real dependency, so versioning it would have no consumer. A changeset never names <code>@sparkforensics/core</code>: it names the public packages the change reaches (<code>sparkforensics-cli</code>, <code>sparkforensics-mcp</code>, <code>sparkforensics-server</code>, <code>sparkforensics-web</code>), and the changeset check fails a note that names core.</p><p>A release itself is two merges, not one: merging your feature PR into <code>main</code> runs <code>.github/workflows/release.yml</code>, which opens (or updates) a &quot;Version Packages&quot; PR collecting all pending changesets. Nothing publishes at this step. Merging <em>that</em> PR lands the version bumps and changelog entries it carries, and the release run that follows runs the full build and test suite, then <code>npm publish</code> for each publishable package whose new version isn&#39;t on npm yet.</p><h3 id="publishing-a-new-package-name-maintainers" tabindex="-1">Publishing a new package name (maintainers) <a class="header-anchor" href="#publishing-a-new-package-name-maintainers" aria-label="Permalink to &quot;Publishing a new package name (maintainers)&quot;">​</a></h3><p>The release workflow publishes through npm Trusted Publishing, which can&#39;t create a package name that doesn&#39;t exist on npmjs.com yet. A new publishable package needs one manual publish from a maintainer&#39;s npm account first, then a Trusted Publisher entry. All five current packages (<code>sparkforensics-cli</code>, <code>sparkforensics-mcp</code>, <code>sparkforensics-server</code>, <code>sparkforensics-analyze</code> and <code>sparkforensics</code>) have their Trusted Publisher entries; this only applies to a package added later. Until it&#39;s done, that package&#39;s publish step fails and the release run exits non-zero; packages that don&#39;t depend on it still publish.</p><p>First add the package to <code>PACKAGES</code> in <code>scripts/publish-packages.mjs</code> and to the path regex in <code>scripts/check-changeset.sh</code> (the comments in both list every place to keep in sync); an alias of the CLI also goes in <code>.changeset/config.json</code>&#39;s <code>fixed</code> group. Then:</p><ol><li><p>From a clean checkout of <code>main</code>, logged in with <code>npm login</code>, publish the new package at the version in its <code>package.json</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> packages/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">new-packag</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &amp;&amp; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> publish</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --access</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div></li><li><p>On npmjs.com, open the package&#39;s <strong>Settings</strong>, add a <strong>Trusted Publisher</strong> for GitHub Actions with organization <code>shuffle-works</code>, repository <code>sparkforensics</code>, workflow filename <code>release.yml</code> and no environment, and save.</p></li><li><p>Optional: in the same settings page, set publishing access to require two-factor authentication and disallow tokens, so only the workflow can publish from then on.</p></li></ol><p><code>scripts/publish-packages.mjs</code> skips a version that&#39;s already on the registry, so the next release run won&#39;t try to republish the bootstrap version.</p><p>For the full setup and test workflow, see <a href="./development-setup.html">Development setup</a> and <a href="./testing.html">Testing &amp; verification</a>.</p>',15)])])}const u=s(t,[["render",n]]);export{g as __pageData,u as default};
@@ -0,0 +1,15 @@
1
+ import{_ as s,o as t,c as i,a5 as o}from"./chunks/framework.DSg0KOwT.js";const k=JSON.parse('{"title":"Development setup","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/development-setup.md","filePath":"contributor-guide/development-setup.md"}'),a={name:"contributor-guide/development-setup.md"};function n(d,e,r,c,h,p){return t(),i("div",null,[...e[0]||(e[0]=[o(`<h1 id="development-setup" tabindex="-1">Development setup <a class="header-anchor" href="#development-setup" aria-label="Permalink to &quot;Development setup&quot;">​</a></h1><p>This page is for working on SparkForensics itself. To run the dashboard as a user, see <a href="./../user-guide/getting-started.html#local-server-mode">Run it locally</a>.</p><p>You need Node 22.18+ (CI uses the version in <code>.nvmrc</code>) and <code>git</code> on your PATH: the first test or build run fetches the tuning reference with it. Clone the repo, then:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> submodule</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> update</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --init</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/log-corpus</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # or: git clone --recurse-submodules</span></span>
2
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> install</span></span>
3
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # vitest run: the root suite (site app and dev tooling)</span></span>
4
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test:watch</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # vitest, watch mode</span></span>
5
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test:core</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # packages/core&#39;s suite: detectors, analyzer, parser, MCP tools</span></span>
6
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test:cli</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # packages/cli&#39;s suite (packs and spawns the real tarball)</span></span>
7
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test:mcp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # packages/mcp&#39;s suite (packs and spawns the real tarball)</span></span>
8
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # Vite dev server for the app itself</span></span>
9
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> build</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # production build, outputs dist/ (plus dist/export-template.html, the HTML download&#39;s template)</span></span>
10
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> preview</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # serves dist/ locally</span></span>
11
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> tsc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --noEmit</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # typecheck (strict TypeScript across src/, tests/view/ and packages/core/src/)</span></span>
12
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> lint</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # eslint over the repo&#39;s JS (.ts/.tsx are covered only by tsc)</span></span>
13
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">node</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> packages/cli/bin/sparkforensics-analyze.mjs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">file</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">rolling-log-dir</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># run the CLI analyzer against a log, outside the browser</span></span></code></pre></div><p><code>npm run test:coverage</code>, at the root or inside <code>packages/core</code>, <code>cli</code>, <code>mcp</code> or <code>server</code>, runs that suite with <code>--coverage</code> (v8 provider, <code>lcov</code> and <code>text</code> reporters, plus <code>html</code> at the root; written to <code>&lt;pkg&gt;/coverage/lcov.info</code>). CI&#39;s <code>build</code>, <code>core</code>, <code>cli</code>, <code>mcp</code> and <code>server</code> jobs all run through it and upload to Coveralls, merged by a final <code>finish</code> job.</p><p>The dashboard&#39;s <strong>Download HTML dashboard</strong> fetches <code>export-template.html</code> from beside the app, so under <code>npm run dev</code> it fails with an error toast; use <code>npm run build &amp;&amp; npm run preview</code> to try it.</p><p><code>npm install</code> also runs the <code>prepare</code> script, which points git at <code>.githooks/</code>. After that, a pre-commit hook lints staged JS files on every commit. Fix what it reports, or skip it with <code>git commit --no-verify</code>.</p><p>The tuning reference under <code>packages/core/src/docs-content/{chapters,tuning,diagrams}</code> isn&#39;t committed. It&#39;s generated from the <code>shuffle-works/spark-tuning-reference</code> commit pinned in <code>packages/core/src/docs-content/upstream.json</code>: the first <code>npm test</code>, <code>npm run docs:dev</code> or <code>npm run build</code> fetches it over HTTPS (about 2s, no credentials) and later runs reuse the gitignored copy while it matches the pin. <code>npm run docs:fetch</code> does the same step on its own. Never edit those folders: fix the content upstream, then <code>npm run docs:bump</code> to move the pin. Running the MCP or CLI bins from source (<code>node packages/*/bin/...</code>) doesn&#39;t fetch it, so run <code>npm run docs:fetch</code> first: without it the MCP&#39;s tuning-reference lookups come back empty. <code>npm run docs:bump [-- &lt;sha&gt;]</code> moves the pin (default: upstream head), regenerates the docs and writes the changeset; put the compare link it prints in the PR body. <code>npm run docs:bump -- --check [&lt;sha&gt;]</code> runs the anchor gate and writes nothing; <code>.github/workflows/tuning-reference-drift.yml</code> runs it with <code>test:core</code> every week.</p><p>Offline, a cached copy keeps working. If the pin has moved since, <code>npm test</code> and <code>docs:dev</code> warn and use the older copy, while <code>docs:build</code>, <code>npm pack</code> and anything with <code>CI</code> set fail. <code>npm run docs:fetch -- --allow-stale</code> keeps a stale cached copy with a warning when the fetch fails; <code>docs:dev</code> uses it. To build from a local upstream checkout instead (offline, or to preview upstream edits in this docs site before they merge), set <code>SPARK_TUNING_REFERENCE_DIR</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">SPARK_TUNING_REFERENCE_DIR</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">../spark-tuning-reference</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> docs:dev</span></span></code></pre></div><p>The same checks apply to that checkout. <code>npm pack</code> and CI accept it only when it sits at the pinned commit with no uncommitted <code>content/</code> changes.</p><p><code>dev/log-corpus</code> is a git submodule pointing at the public <code>spark-event-corpus-data</code> repo. It&#39;s optional locally: if you skip the <code>git submodule update</code> step above, the corpus-backed tests (such as <code>packages/server/test/shs-proxy-fixture.test.js</code>) report as skipped rather than failed, which is expected, not a bug. CI&#39;s <code>core</code> and <code>server</code> jobs check it out and run them (plus the <a href="./testing.html#corpus-regression-snapshot">corpus regression snapshot</a> in <code>core</code>); the <code>build</code>, <code>cli</code> and <code>mcp</code> jobs don&#39;t, so a corpus-backed test belongs in <code>packages/core</code> or <code>packages/server</code>.</p><p>The <code>packages/server/</code> package (the optional local-server deploy mode) keeps its own dependencies and test suite. Run it from the repo root: a second <code>npm install</code> inside <code>packages/server</code> prunes the shared root <code>node_modules</code>&#39;s workspace symlinks.</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> test:server</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # runs packages/server&#39;s own suite, not part of \`npm test\`</span></span></code></pre></div><p>Packing or releasing <code>packages/server/</code> builds the docs site for you: its <code>prepack</code> runs <code>packages/server/scripts/copy-frontend.js</code>, which runs the root <code>npm run build</code> (<code>docs:build</code> included), so the packed artifact always ships <code>/docs/</code>.</p><p>This docs site has its own dev loop:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> docs:dev</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # VitePress dev server for docs-site/</span></span>
14
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> docs:build</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # production build; also checks internal links/anchors</span></span>
15
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> docs:preview</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # serves the built docs site locally</span></span></code></pre></div><h2 id="generated-and-vendored-files" tabindex="-1">Generated and vendored files <a class="header-anchor" href="#generated-and-vendored-files" aria-label="Permalink to &quot;Generated and vendored files&quot;">​</a></h2><ul><li><code>packages/core/src/docs-content/detection/*.md</code> (gitignored) is split from <code>docs-site/user-guide/understanding-findings.md</code>, one file per finding tag. <code>docs:dev</code>, <code>docs:build</code>, the vitest globalSetup and <code>prepack</code> regenerate it when it differs from the guide, so nothing is committed or run by hand. To regenerate on demand: <code>npm run split-detection-docs</code>.</li><li><code>packages/{cli,mcp,server}/vendor-core/</code> (gitignored) is rebuilt only at <code>prepack</code>. In the monorepo the bins load <code>packages/core/src/load-vendored.js</code>, which uses a leftover <code>vendor-core/</code> only while its <code>core-source-hash.txt</code> matches <code>packages/core/src</code>; otherwise it warns on stderr and runs the source. <code>load-vendored.js</code> is the one module a bin locates by hand (<code>vendor-core/load-vendored.js</code> in a packed install); the bin loads every other core module through its exported <code>loadVendored()</code>.</li><li>The CLI&#39;s <code>export-template/</code> has no such check. Without it, <code>--export-html</code> from source uses the repo-root <code>dist-export/</code> from <code>npm run build:export</code> (not produced by <code>npm run build</code>); a leftover <code>export-template/</code> wins over that, so rebuild it with <code>node scripts/vendor-export-template.mjs</code> or delete it before trusting a local <code>--export-html</code>.</li></ul><h2 id="the-docs-site-s-anchor-clicks" tabindex="-1">The docs site&#39;s anchor clicks <a class="header-anchor" href="#the-docs-site-s-anchor-clicks" aria-label="Permalink to &quot;The docs site&#39;s anchor clicks&quot;">​</a></h2><p>VitePress installs a <code>window</code>-level, capture-phase click listener (<code>node_modules/vitepress/dist/client/app/router.js</code>) that catches clicks on any <code>&lt;a href=&quot;#...&quot;&gt;</code> pointing at the current page and scrolls to the target itself, before a handler the theme adds on that element runs, so <code>preventDefault()</code> there is too late. An interactive control built over in-page anchors, such as the footnote-marker popovers in <code>docs-site/.vitepress/theme/citation-chips.ts</code>, must drop the element&#39;s <code>href</code> and restore <code>role</code>, <code>tabindex</code> and keyboard handling by hand.</p><p>That router scrolls with its own offset, while a deep link, and every click on the published tuning reference (which ships without hydration), is a native fragment jump that follows <code>scroll-padding-top</code>. Both read one measured value: <code>docs-site/.vitepress/anchor-offset.js</code>, inlined into every page&#39;s head, sizes it from the pinned chrome on screen, which includes the Shuffle Works product bar on the published site. Never write that bar&#39;s marker attribute literally in the docs source, prose included: the hub skips injecting its bar into any built page that contains it.</p><p>The app side of that contract is three DOM markers, all read in <code>src/view/shuffle-hub.ts</code> (<code>PRODUCT_BAR_SELECTOR</code>, <code>HUB_FOOTER_SELECTOR</code>) and set on the dashboard, plan graph and run comparison routes. Their exact attribute names live in that file, not here, for the reason above.</p><ul><li>The product-bar marker (a <code>data-shuffle-</code> attribute ending in <code>product-bar</code>) is present when the host site injected its shared bar into the published shell. <code>ProductBarPortal</code> portals the page&#39;s header controls into it; without it they render in place.</li><li>The footer marker (a <code>data-shuffle-</code> attribute ending in <code>footer</code>) is present when the host site injected its footer.</li><li>The <code>dashboard</code> test id (<code>data-testid=&quot;dashboard&quot;</code>) is the signal the host site&#39;s chrome-toggle script uses to hide the shared nav and footer while a dashboard is on screen. Each of those routes carries it, and <code>App</code> renders only one at a time, so it stays present across route changes.</li></ul><h2 id="driving-the-app-in-a-browser" tabindex="-1">Driving the app in a browser <a class="header-anchor" href="#driving-the-app-in-a-browser" aria-label="Permalink to &quot;Driving the app in a browser&quot;">​</a></h2><ul><li>Sample logs: download one from a Spark History Server with <code>curl -o app.zip &quot;&lt;baseUrl&gt;/api/v1/applications/&lt;appId&gt;/logs&quot;</code> and drop the zip in as-is (single-file and rolling logs both unwrap), or the <code>.zstd</code> event log inside it. Private logs follow the naming rule in <a href="./testing.html">Testing</a>.</li><li>Loading a file under Playwright: the drop zone prefers <code>showOpenFilePicker</code>, a native dialog that localhost Chromium can&#39;t drive. Run <code>await page.evaluate(() =&gt; delete window.showOpenFilePicker)</code> before clicking <strong>Choose file</strong>, then <code>setInputFiles</code> on <code>[data-testid=file-input]</code> or handle the <code>filechooser</code> event.</li><li>Run comparison: on the landing page, click <strong>Compare two runs</strong>, fill the <strong>Baseline</strong> and <strong>Candidate</strong> slots the same way (each has its own <code>[data-testid=file-input]</code>), then click <strong>Compare</strong>. Both runs parse one after the other through the one worker. From a run&#39;s dashboard, the topbar&#39;s <strong>Compare with another run</strong> opens the same view with that run in the baseline slot (a <code>cached</code> <code>RunSource</code>, not parsed again). <strong>View baseline/candidate dashboard</strong> drills into one run, and <strong>← Back to comparison</strong> returns.</li><li>The landing page&#39;s <strong>Try a sample run</strong> (<code>src/view/DropZone.tsx</code>) loads a gzip-compressed corpus log from <code>public/sample-runs/</code>: the corpus candidate with the most findings under <code>sparkforensics-analyze --format json</code>. Rerun that scan over every candidate, against the current corpus, before swapping the bundled sample.</li><li>Screenshots on a PR: the <code>gh</code> token can&#39;t use GitHub&#39;s browser-only attachment uploader. Commit the PNGs to a throwaway asset branch and embed them by commit SHA as <code>https://raw.githubusercontent.com/&lt;owner&gt;/&lt;repo&gt;/&lt;sha&gt;/&lt;path&gt;.png</code>. The repo is public, so every reader&#39;s browser loads them, and the binaries stay out of the feature diff.</li><li>Seeing what the Advanced density tier gates: set <code>DEBUG_HIGHLIGHT</code> to <code>true</code> in <code>src/view/AdvancedOnly.tsx</code> to outline <code>AdvancedOnly</code> content in red. Keep it <code>false</code> in anything you commit.</li></ul>`,26)])])}const u=s(a,[["render",n]]);export{k as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as s,o as t,c as i,a5 as o}from"./chunks/framework.DSg0KOwT.js";const k=JSON.parse('{"title":"Development setup","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/development-setup.md","filePath":"contributor-guide/development-setup.md"}'),a={name:"contributor-guide/development-setup.md"};function n(d,e,r,c,h,p){return t(),i("div",null,[...e[0]||(e[0]=[o("",26)])])}const u=s(a,[["render",n]]);export{k as __pageData,u as default};
@@ -0,0 +1,4 @@
1
+ import{_ as t,o,c as s,a5 as a}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Testing & verification","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/testing.md","filePath":"contributor-guide/testing.md"}'),d={name:"contributor-guide/testing.md"};function i(n,e,r,c,l,h){return o(),s("div",null,[...e[0]||(e[0]=[a(`<h1 id="testing-verification" tabindex="-1">Testing &amp; verification <a class="header-anchor" href="#testing-verification" aria-label="Permalink to &quot;Testing &amp; verification&quot;">​</a></h1><p>Before opening a PR:</p><ol><li>Run the full suite: <code>npm test</code> (root), plus <code>npm run test:core</code>, <code>npm run test:cli</code>, <code>npm run test:mcp</code> and <code>npm run test:server</code> for the packages you touched. Each of those four packages has its own Vitest config and suite, none of them covered by the root <code>npm test</code>. Run <code>npm run build</code> first on a fresh checkout: <code>tests/vite-entry.test.js</code> checks the production bundle in <code>dist/</code>.</li><li>Typecheck: <code>npx tsc --noEmit</code>.</li><li>Lint: <code>npm run lint</code>. A pre-commit hook (<code>.githooks/pre-commit</code>) runs eslint on staged <code>.js</code>/<code>.mjs</code> files only, so run the full lint yourself before committing. Neither lints <code>.ts</code>/<code>.tsx</code> (see <code>eslint.config.js</code>): <code>tsc</code> is their only check.</li><li>For any UI change, verify it in a real browser (Playwright or headless Chrome). Unit and jsdom tests check code correctness; they don&#39;t prove a feature works end-to-end. Drive the app, exercise the golden path and the edge cases, and look for regressions elsewhere on the dashboard.</li><li>Report the passing test count when you&#39;re done, not &quot;tests pass.&quot;</li></ol><p>Sample Spark event logs for manual testing live outside this repo. Keep them in a sibling <code>../spark-log-examples/</code> directory: real <code>.zstd</code> event logs plus several <code>run-compare-*-baseline-*</code>/<code>run-compare-*-candidate-*</code> pairs for run comparison. They are private and this repo is public: never put their file names, app IDs, app names, tables or paths in tracked files, commits or PR text. Use the neutral <code>private-log-NN</code> labels under <a href="#test-fixtures">Test fixtures</a>, and synthetic IDs such as <code>application_0000000000000_0001</code>.</p><h2 id="testing-layout" tabindex="-1">Testing layout <a class="header-anchor" href="#testing-layout" aria-label="Permalink to &quot;Testing layout&quot;">​</a></h2><p>Vitest throughout. Core logic tests (<code>packages/core/test/*.test.js</code>, covering parser, analyzer, detectors, format-utils, etc.) run under Node via <code>npm run test:core</code>. The root <code>npm test</code> runs two environments in one run: root-level tooling tests (<code>tests/*.test.js</code>, <code>tests/*.test.ts</code>) and pure view-logic tests (<code>tests/view/*.test.ts</code>) under Node, and component tests (<code>tests/view/*.test.tsx</code>), which declare <code>// @vitest-environment jsdom</code> and use React Testing Library (<code>render</code>/<code>screen</code>/<code>userEvent</code>). Mount components through React Testing Library, not hand-rolled DOM code.</p><p><code>packages/cli</code>, <code>packages/mcp</code> and <code>packages/server</code> are separate npm packages, each with its own Vitest config and suite, not run by the root suite. Each has a pack-and-spawn test (for example <code>packages/server/test/bin.test.js</code>) that packs and installs the real published tarball and spawns the real <code>.bin</code> entry point, exercising the production build end-to-end.</p><p><code>npm run test:core</code>, <code>test:cli</code> and <code>test:mcp</code> run with v8 coverage; CI uploads every suite&#39;s lcov to Coveralls. No coverage threshold is enforced (see the comment in <code>vitest.config.js</code>).</p><p>Two checks guard invariants that a future edit could silently break. The <code>detector contract</code> suite in <code>packages/core/test/analyzer.test.js</code> asserts every <code>DETECTORS</code> entry&#39;s contract fields, that <code>detectorInfoByType</code> covers exactly the emitted types, that each entry&#39;s thresholds are frozen, and that the only <code>suppressedBy</code> pair is <code>stageSlowness</code> -&gt; <code>slowHost</code>, both stage-scope (see <a href="./architecture/detector-contract.html#cross-detector-suppression">Cross-detector suppression</a>; declaration order does not matter). <code>REGISTRY</code> and <code>FINDING_PRESENTATION</code> completeness is a compile-time check, not a test: both are typed against the emitted <code>FindingType</code> union, so <code>npx tsc --noEmit</code> fails on a missing or extra key (see <a href="./architecture/detector-contract.html#detector-contract">Detector contract</a>).</p><h2 id="test-fixtures" tabindex="-1">Test fixtures <a class="header-anchor" href="#test-fixtures" aria-label="Permalink to &quot;Test fixtures&quot;">​</a></h2><p>Real Spark event logs from private workloads live under <code>examples/</code>, gitignored: they&#39;re large, and their file names and contents identify the jobs that produced them, so they never enter this public repo. Tests, docs and scripts refer to them only by the neutral labels below. A test that reads one skips when its file is missing, so the suite passes without any of them. To run those tests, copy or symlink your own logs into <code>examples/</code> under these names:</p><table tabindex="0"><thead><tr><th>Label</th><th>Log it stands for</th><th>Used by</th></tr></thead><tbody><tr><td><code>private-log-01.zstd</code></td><td>~12 MB zstd run with Plan Advisor findings</td><td><code>impact-estimator-real-log.test.js</code>, <code>plan-node-stage-mapping-real-log.test.js</code>, spot-checks in <a href="./architecture/impact-estimation.html">Impact estimation</a></td></tr><tr><td><code>private-log-02.zstd</code></td><td>~2 MB zstd run with a heavy shuffle-and-spill stage</td><td><code>plan-node-stage-mapping-real-log.test.js</code>, spot-checks in <a href="./architecture/impact-estimation.html">Impact estimation</a></td></tr><tr><td><code>private-log-03.zstd</code></td><td>~28 MB zstd run with about 1,200 stages</td><td>timing spot-check in <a href="./architecture/impact-estimation.html">Impact estimation</a></td></tr><tr><td><code>private-log-04.zstd</code></td><td>~9 MB zstd run whose stage 394 plan became <code>plan-graph-stage-394.json</code></td><td><code>scripts/extract-plan-fixture.mjs</code></td></tr><tr><td><code>private-log-05</code></td><td>~76 MB uncompressed NDJSON run, 62 stages</td><td><code>parser-worker.test.js</code>; also a fast end-to-end smoke test</td></tr><tr><td><code>private-log-06</code></td><td>~1.3 GB uncompressed NDJSON run</td><td><code>detectors-plan.test.js</code>; also a stress test for the Web Worker streaming path</td></tr><tr><td><code>private-log-07</code></td><td>~120 MB log from a Spark History Server download</td><td>none: check the <code>LZ4Block</code> codec decoder by hand; <code>packages/core/test/lz4-block.test.js</code> uses a small hardcoded byte array instead</td></tr></tbody></table><p><code>scripts/extract-plan-fixture.mjs</code> anonymizes every table, column, path and literal before it writes the committed fixture; keep it that way if you extract another one.</p><p>Of the finding types <code>retryWaste</code>, <code>autoscalingChurn</code>, <code>failures</code>, <code>stageFailed</code> and <code>jobFailureRate</code>, only <code>retryWaste</code> and <code>autoscalingChurn</code> fire on any of the real logs in <code>../spark-log-examples/</code>. <code>failures</code>, <code>stageFailed</code>, and <code>jobFailureRate</code> fire on none of them; they are covered by unit tests only (<code>tests/view/task-failures.test.tsx</code>, <code>tests/view/stage-failed.test.tsx</code>, <code>tests/view/job-failures.test.tsx</code>), not against a real log.</p><p><code>dev/log-corpus/</code> is a git submodule pointing at the public <code>spark-event-corpus-data</code> repo, pinned to a tag. It backs the corpus-backed tests in <code>packages/core/test/</code> (for example <code>list-runs.test.js</code>, <code>task-failure.test.js</code>) and <code>packages/server/test/shs-proxy-fixture.test.js</code> (which loads a real <code>*-parquet-baseline.ndjson</code> fixture and asserts the <code>/shs-proxy</code> route streams it back unmodified), plus the <a href="#corpus-regression-snapshot">corpus regression snapshot</a>. Populate it locally with <code>git submodule update --init dev/log-corpus</code>; without it, those tests skip and the snapshot check has no logs (see <a href="./development-setup.html">Development setup</a>). CI&#39;s <code>core</code>, <code>server</code> and <code>node18</code> jobs check it out.</p><p><code>packages/core/test/fixtures/aqe-skew-spark-3.5.ndjson</code> and <code>aqe-skew-spark-4.0.ndjson</code> are real local-mode runs (Spark 3.5.9 and 4.0.4) of three queries over 1M rows with 60% on one key, joined to a 2,000-row table with auto-broadcast off, 16 shuffle partitions, a 2 MB skew threshold and a 1 MB advisory size set with <code>spark.conf.set</code>: an inner join AQE split, a left outer join with the skew on the right side, and an inner join over <code>repartition(&quot;k&quot;)</code>. <code>aqe-skew.test.js</code> reads them. They were cut down for the repo: <code>TaskStart</code> events, the accumulables of every task but the first of each stage, stage-info details, environment properties and host names were removed, and the tasks of stages that read no shuffle were dropped. Regenerate one the same way: run the job with <code>apache/spark:&lt;version&gt;</code> in <code>local[4]</code> with an uncompressed, non-rolling event log, and trim it with the same removals.</p><h3 id="corpus-regression-snapshot" tabindex="-1">Corpus regression snapshot <a class="header-anchor" href="#corpus-regression-snapshot" aria-label="Permalink to &quot;Corpus regression snapshot&quot;">​</a></h3><p><code>dev/corpus-snapshot.json</code> records every finding (type, location, band, confidence, value, impact estimate) that the detectors produce on each public corpus log, plus its stage, SQL execution and skipped-line counts, with timings and memory left out so it is deterministic. CI&#39;s <code>core</code> job re-analyzes the corpus and fails on any difference, printing the added, removed, re-banded and re-estimated findings and changed counts per log:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">node</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/bench-analyze.mjs</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --check</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/corpus-snapshot.json</span></span></code></pre></div><p>When a detector, threshold or estimator change is meant to alter findings, read that diff, confirm each change is the one you intended, then refresh the snapshot and commit it with the change:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">node</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/bench-analyze.mjs</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --update</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/corpus-snapshot.json</span></span></code></pre></div><p>Both default to <code>dev/log-corpus/logs</code> and its <code>external/</code> folder. A directory argument expands one level (every regular file, sorted), except a Spark rolling-log directory (<code>events_&lt;n&gt;_*</code> files), which is analyzed as one run. Only public corpus logs belong in the snapshot: never pass private logs to <code>--update</code>.</p><h3 id="detector-and-estimate-tools" tabindex="-1">Detector and estimate tools <a class="header-anchor" href="#detector-and-estimate-tools" aria-label="Permalink to &quot;Detector and estimate tools&quot;">​</a></h3><p>Record before and after numbers from these in the commit that changes a detector, threshold, estimate or the vendored fzstd.</p><ul><li><code>node dev/bench-analyze.mjs [--repeat N] --out snap.json &lt;file|dir&gt;...</code> snapshots every finding (band, estimate) plus parse and analyze timings, one child process per log. <code>--diff a.json b.json [--verbose]</code> shows the findings added, removed or re-banded between two snapshots.</li><li><code>node --max-old-space-size=12000 dev/eval-tail-replay.mjs [--set detector.threshold=value] [--verbose] &lt;file|dir&gt;...</code> scores skew and straggler against a task-level replay (precision, recall, estimate error). <code>--verbose</code> lists each miss, false positive and estimate more than 2x off. For every stage with 2+ tasks it replays the tasks with list scheduling (slots = the stage&#39;s observed peak concurrency, tasks in launch order) twice: with real durations, and with every task over 4x P50 capped at P50. A stage is a positive when the difference is at least 0.5% of app runtime. The replay is independent of the detectors&#39; own <code>computeTailReplayRecoveryMs</code>, so it checks that claim.</li><li><code>node dev/fuzz-fzstd.mjs --upstream &lt;pristine fzstd esm/index.mjs&gt; &lt;log.zstd&gt;...</code> checks the locally patched <code>packages/core/src/vendor/fzstd.js</code> against upstream, on whole logs and on randomly corrupted prefixes. Run it after any fzstd edit. <code>--iterations N</code>, <code>--seed N</code> and <code>--mutations N</code> tune the random cases; it exits 1 when any case differs. Whole-log output must be byte-identical, and on corrupted or truncated prefixes the partial output and the error must match. The one known difference: a corrupt window size that overflows negative throws &quot;Invalid typed array length&quot; at the same byte in both, with a different number in the message. Get upstream with <code>npm pack fzstd@0.1.1</code> and extract <code>package/esm/index.mjs</code> (its sha256 is in the vendored file&#39;s header).</li><li><code>node dev/bench-extraction-coverage.mjs [corpus-dir] [--format json] [--strict]</code> reports how much of the corpus&#39;s event-log content becomes structured data. Each non-blank line of an included event type lands in one bucket: <code>extracted</code> (known event, passes its schema), <code>unmodeled</code> (valid JSON whose <code>Event</code> is not a modeled one), <code>schemaFail</code> (known event that fails its own schema, which signals drift against real Spark output) or <code>malformed</code> (not JSON). <code>--format json</code> prints a machine-readable summary and <code>--strict</code> exits 1 on any <code>schemaFail</code>.</li><li><code>node dev/bench-event-validation.mjs [fixture-dir]</code> measures the throughput that per-line <code>SparkEventSchema.safeParse</code> adds on top of <code>JSON.parse</code> on the largest fixture, decompressed in memory first.</li></ul><p>CI also runs a <code>node18</code> job, because the published <code>cli</code>, <code>mcp</code> and <code>server</code> packages declare <code>engines.node &gt;=18</code> while vitest needs Node 22+. It packs the three tarballs on <code>.nvmrc</code>&#39;s Node, installs them on Node 18, runs the CLI over every corpus log plus a zstd copy of one, checks that the MCP server answers <code>initialize</code>, and starts <code>sparkforensics-server</code> to check it serves the app and answers <code>initialize</code> on <code>/mcp</code>.</p><h3 id="benchmarking-and-output-identity" tabindex="-1">Benchmarking and output identity <a class="header-anchor" href="#benchmarking-and-output-identity" aria-label="Permalink to &quot;Benchmarking and output identity&quot;">​</a></h3><p>These tools measure a performance change and prove it left the analysis output alone. The first two need no dependencies beyond Node. <code>bench-dashboard</code> uses Playwright. Run them on a quiet machine (<code>cat /proc/loadavg</code>) and report the load average next to the numbers.</p><ul><li><p><code>node dev/bench-phases.mjs [--repeat N] [--json out.json] &lt;log&gt;[:&lt;baseline&gt;]...</code> times each phase in a fresh <code>node</code> child per repeat (cold JIT, as in a real CLI run): decompress, collect (parse and model build), findings, evidence report, the report&#39;s output blocks and, for a <code>log:baseline</code> pair, the comparison. It prints median, minimum and maximum. <code>--cli</code> times the CLI end to end, and <code>--mcp</code> times the MCP tool functions cold and warm. Prefer the in-process phase timers over wall clock when other work shares the machine.</p></li><li><p><code>node dev/bench-dashboard.mjs [--repeat N] [--json out.json] [--dist dir] [--no-tabs] &lt;log&gt;...</code> drives the production build (<code>npx vite build</code> first) in a real browser, one fresh context per repeat, and reports the median time from dropping the log to the first rendered dashboard, to the point no lazy-widget skeleton is left, the main-thread long tasks in between, the JS heap, the peak resident memory of the whole browser, and the long tasks of each report-tab switch. <code>--dist</code> serves another build output, so a baseline build copied aside can be interleaved with the branch build. <code>BENCH_BROWSER_CHANNEL=chrome</code> uses the system Chrome when Playwright&#39;s own browser is not installed. It starts and stops its own <code>vite preview</code>.</p></li><li><p><code>node dev/bench-graph-layout.mjs [--repeat N] [--top K] [--scope full|segment] &lt;log&gt;...</code> builds each stage&#39;s plan graph and times the dagre layout the graph view runs for the heaviest ones: a cold layout, then the layout after a duration-mode flip (same topology, new node data). A plan whose layout throws is reported as such. Logs are labelled by position, not by name.</p></li><li><p><code>dev/snapshot-cli-output.sh &lt;out-dir&gt; &lt;log&gt;... [-- &lt;baseline&gt;:&lt;candidate&gt;...]</code> writes the CLI&#39;s JSON report for every log and a comparison for every pair, with the <code>generator</code> block removed (its build id hashes the core sources). Run it on the base commit and on the branch, then compare:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dev/snapshot-cli-output.sh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/before</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/log-corpus/logs/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">*</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.ndjson</span></span>
2
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> switch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> my-branch</span></span>
3
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dev/snapshot-cli-output.sh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/after</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev/log-corpus/logs/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">*</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.ndjson</span></span>
4
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">diff</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/before</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/after</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &amp;&amp; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> IDENTICAL</span></span></code></pre></div><p>State the result and the number of outputs compared in the PR.</p></li></ul><h2 id="test-suite-growth-discipline" tabindex="-1">Test-suite growth discipline <a class="header-anchor" href="#test-suite-growth-discipline" aria-label="Permalink to &quot;Test-suite growth discipline&quot;">​</a></h2><p>Widget and detector test files collect copy-pasted boilerplate fast. Before adding a widget test file, or a per-widget or per-detector test:</p><ul><li>Cross-cutting widget behavior (impact/stage sort-toggle default and flip, density-gated visibility, 6-at-a-time pagination) belongs in a shared helper under <code>tests/view/_shared/</code>, called from each widget&#39;s test file. It holds <code>testFinding</code> (<code>finding.ts</code>), <code>installInterpretation</code> and the sort-order helpers (<code>sort-order-toggle.ts</code>); add a density or pagination helper there the first time two widget tests would share one. Extract into it only when a test body is identical to an existing one apart from the widget name, fixture or label. A widget with a real difference (a <code>React.lazy</code>-loaded chunk, a custom row finder) keeps its own test.</li><li><code>packages/core/test/fixtures/</code> holds the shared stage and app fixture factories (<code>makeStage</code>/<code>makeApp</code>). Import them instead of pasting a local copy into a new core test file.</li><li>Before adding a small test file, check whether a sibling file&#39;s <code>describe</code> block already covers that surface. A detector-catalog shape check, for example, belongs in <code>analyzer.test.js</code>&#39;s <code>detector contract</code> block.</li><li>Calling an exported pure function directly, or calling the documented event reducer (<code>processEvent</code> in <code>packages/core/src/event-handlers.ts</code>, re-exported from <code>parser-worker.ts</code>; see the <a href="./architecture/worker-protocol.html">worker protocol</a>) with one event and inspecting the state, is normal unit testing. Don&#39;t rewrite reducer-style tests into NDJSON-through-<code>runParse</code> integration tests to avoid touching internals: that costs readability for no real safety.</li></ul>`,32)])])}const g=t(d,[["render",i]]);export{u as __pageData,g as default};
@@ -1 +1 @@
1
- import{_ as t,o,c as s,a5 as a}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Testing & verification","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/testing.md","filePath":"contributor-guide/testing.md"}'),d={name:"contributor-guide/testing.md"};function c(r,e,i,n,l,p){return o(),s("div",null,[...e[0]||(e[0]=[a("",28)])])}const g=t(d,[["render",c]]);export{u as __pageData,g as default};
1
+ import{_ as t,o,c as s,a5 as a}from"./chunks/framework.DSg0KOwT.js";const u=JSON.parse('{"title":"Testing & verification","description":"","frontmatter":{},"headers":[],"relativePath":"contributor-guide/testing.md","filePath":"contributor-guide/testing.md"}'),d={name:"contributor-guide/testing.md"};function i(n,e,r,c,l,h){return o(),s("div",null,[...e[0]||(e[0]=[a("",32)])])}const g=t(d,[["render",i]]);export{u as __pageData,g as default};
@@ -1 +1 @@
1
- import{_ as e,o as t,c as r}from"./chunks/framework.DSg0KOwT.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"SparkForensics","text":"Docs","tagline":"Find out why your Spark job is slow, ranked by which fix saves the most time."},"features":[{"title":"User Guide","details":"Load a run, read the findings, compare two runs, use the MCP tools.","link":"/user-guide/getting-started","linkText":"Get started"},{"title":"Contributor Guide","details":"Set up the project, read the architecture, run the tests, propose a change.","link":"/contributor-guide/development-setup","linkText":"Start contributing"}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),a={name:"index.md"};function i(o,n,s,d,c,u){return t(),r("div")}const h=e(a,[["render",i]]);export{p as __pageData,h as default};
1
+ import{_ as e,o as t,c as n}from"./chunks/framework.DSg0KOwT.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"SparkForensics","text":"Docs","tagline":"Find out why your Spark job is slow, ranked by which fix saves the most time."},"features":[{"title":"User Guide","details":"Load a run, read the findings, compare two runs, use the MCP tools.","link":"/user-guide/getting-started","linkText":"Get started"},{"title":"Contributor Guide","details":"Set up the project, read the architecture, run the tests, propose a change.","link":"/contributor-guide/development-setup","linkText":"Start contributing"},{"title":"Tuning Reference","details":"Spark architecture, memory, shuffle and tuning notes behind every finding.","link":"/tuning-reference/","linkText":"Open the reference"}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),r={name:"index.md"};function i(a,o,s,d,c,u){return t(),n("div")}const h=e(r,[["render",i]]);export{p as __pageData,h as default};
@@ -1 +1 @@
1
- import{_ as e,o as t,c as r}from"./chunks/framework.DSg0KOwT.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"SparkForensics","text":"Docs","tagline":"Find out why your Spark job is slow, ranked by which fix saves the most time."},"features":[{"title":"User Guide","details":"Load a run, read the findings, compare two runs, use the MCP tools.","link":"/user-guide/getting-started","linkText":"Get started"},{"title":"Contributor Guide","details":"Set up the project, read the architecture, run the tests, propose a change.","link":"/contributor-guide/development-setup","linkText":"Start contributing"}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),a={name:"index.md"};function i(o,n,s,d,c,u){return t(),r("div")}const h=e(a,[["render",i]]);export{p as __pageData,h as default};
1
+ import{_ as e,o as t,c as n}from"./chunks/framework.DSg0KOwT.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"SparkForensics","text":"Docs","tagline":"Find out why your Spark job is slow, ranked by which fix saves the most time."},"features":[{"title":"User Guide","details":"Load a run, read the findings, compare two runs, use the MCP tools.","link":"/user-guide/getting-started","linkText":"Get started"},{"title":"Contributor Guide","details":"Set up the project, read the architecture, run the tests, propose a change.","link":"/contributor-guide/development-setup","linkText":"Start contributing"},{"title":"Tuning Reference","details":"Spark architecture, memory, shuffle and tuning notes behind every finding.","link":"/tuning-reference/","linkText":"Open the reference"}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),r={name:"index.md"};function i(a,o,s,d,c,u){return t(),n("div")}const h=e(r,[["render",i]]);export{p as __pageData,h as default};