@hominis/fireforge 0.35.0 → 0.37.0

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 (369) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +34 -9
  3. package/dist/bin/fireforge.js +21 -6
  4. package/dist/src/cli.js +22 -5
  5. package/dist/src/commands/bootstrap-checks.js +0 -1
  6. package/dist/src/commands/bootstrap.d.ts +2 -1
  7. package/dist/src/commands/bootstrap.js +16 -7
  8. package/dist/src/commands/build.js +5 -4
  9. package/dist/src/commands/config.js +30 -10
  10. package/dist/src/commands/discard.js +18 -3
  11. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  12. package/dist/src/commands/doctor-check-core.js +0 -1
  13. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  14. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  15. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  16. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  17. package/dist/src/commands/doctor-furnace.js +2 -2
  18. package/dist/src/commands/doctor-orphaned-harness.d.ts +52 -0
  19. package/dist/src/commands/doctor-orphaned-harness.js +132 -0
  20. package/dist/src/commands/doctor-working-tree.js +0 -1
  21. package/dist/src/commands/doctor.js +6 -1
  22. package/dist/src/commands/download.js +28 -13
  23. package/dist/src/commands/export-all.js +5 -3
  24. package/dist/src/commands/export-flow.js +12 -7
  25. package/dist/src/commands/export-placement-gate.js +0 -1
  26. package/dist/src/commands/export-placement-policy.js +0 -1
  27. package/dist/src/commands/export-shared.d.ts +5 -1
  28. package/dist/src/commands/export-shared.js +18 -6
  29. package/dist/src/commands/export.js +22 -10
  30. package/dist/src/commands/furnace/apply.js +22 -6
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  34. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  35. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  36. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  37. package/dist/src/commands/furnace/create-features.js +0 -1
  38. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  39. package/dist/src/commands/furnace/create-readback.js +0 -1
  40. package/dist/src/commands/furnace/create-templates.js +0 -1
  41. package/dist/src/commands/furnace/create-validation.js +0 -1
  42. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  43. package/dist/src/commands/furnace/create.js +0 -1
  44. package/dist/src/commands/furnace/deploy.js +7 -83
  45. package/dist/src/commands/furnace/diff.js +0 -1
  46. package/dist/src/commands/furnace/index.js +59 -25
  47. package/dist/src/commands/furnace/init.js +0 -1
  48. package/dist/src/commands/furnace/list.js +0 -1
  49. package/dist/src/commands/furnace/override.js +0 -1
  50. package/dist/src/commands/furnace/preview.js +3 -3
  51. package/dist/src/commands/furnace/refresh.js +0 -1
  52. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  53. package/dist/src/commands/furnace/remove-state.js +47 -1
  54. package/dist/src/commands/furnace/remove.js +3 -9
  55. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  56. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  57. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  58. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  59. package/dist/src/commands/furnace/rename.js +15 -36
  60. package/dist/src/commands/furnace/scan.js +0 -1
  61. package/dist/src/commands/furnace/status.js +0 -1
  62. package/dist/src/commands/furnace/sync.js +76 -4
  63. package/dist/src/commands/furnace/validate.js +0 -1
  64. package/dist/src/commands/furnace/validation-output.js +0 -1
  65. package/dist/src/commands/import.js +35 -9
  66. package/dist/src/commands/lint-per-patch.js +19 -7
  67. package/dist/src/commands/lint.js +13 -3
  68. package/dist/src/commands/manifest.js +1 -2
  69. package/dist/src/commands/package.js +2 -1
  70. package/dist/src/commands/patch/compact.js +1 -1
  71. package/dist/src/commands/patch/delete.js +1 -1
  72. package/dist/src/commands/patch/index.js +1 -1
  73. package/dist/src/commands/patch/lint-ignore.js +21 -6
  74. package/dist/src/commands/patch/move-files.js +1 -1
  75. package/dist/src/commands/patch/patch-context.js +0 -1
  76. package/dist/src/commands/patch/rename.js +4 -4
  77. package/dist/src/commands/patch/reorder.js +3 -4
  78. package/dist/src/commands/patch/split-plan.js +2 -1
  79. package/dist/src/commands/patch/split.js +4 -4
  80. package/dist/src/commands/patch/staged-dependency.d.ts +1 -1
  81. package/dist/src/commands/patch/staged-dependency.js +114 -52
  82. package/dist/src/commands/patch/tier.js +18 -5
  83. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  84. package/dist/src/commands/re-export-files.js +5 -1
  85. package/dist/src/commands/re-export-options.js +0 -1
  86. package/dist/src/commands/re-export-scan.js +0 -1
  87. package/dist/src/commands/re-export.js +14 -4
  88. package/dist/src/commands/rebase/abort.js +2 -9
  89. package/dist/src/commands/rebase/confirm.js +0 -1
  90. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  91. package/dist/src/commands/rebase/continue.js +0 -1
  92. package/dist/src/commands/rebase/index.d.ts +1 -1
  93. package/dist/src/commands/rebase/index.js +10 -15
  94. package/dist/src/commands/rebase/patch-loop.js +38 -7
  95. package/dist/src/commands/rebase/summary.js +2 -3
  96. package/dist/src/commands/rebase.js +0 -1
  97. package/dist/src/commands/register.js +1 -1
  98. package/dist/src/commands/reset.js +16 -9
  99. package/dist/src/commands/resolve.js +2 -2
  100. package/dist/src/commands/run.js +4 -4
  101. package/dist/src/commands/setup-support.js +0 -1
  102. package/dist/src/commands/setup.js +1 -2
  103. package/dist/src/commands/source.js +1 -2
  104. package/dist/src/commands/status-output.js +1 -12
  105. package/dist/src/commands/status.js +147 -133
  106. package/dist/src/commands/test-appdir.js +0 -1
  107. package/dist/src/commands/test-diagnose.js +38 -3
  108. package/dist/src/commands/test-modes.d.ts +15 -0
  109. package/dist/src/commands/test-modes.js +61 -0
  110. package/dist/src/commands/test-register.d.ts +4 -0
  111. package/dist/src/commands/test-register.js +64 -0
  112. package/dist/src/commands/test-run.d.ts +23 -7
  113. package/dist/src/commands/test-run.js +15 -14
  114. package/dist/src/commands/test.d.ts +0 -4
  115. package/dist/src/commands/test.js +147 -96
  116. package/dist/src/commands/token-coverage.js +0 -1
  117. package/dist/src/commands/token.js +1 -2
  118. package/dist/src/commands/typecheck.js +1 -1
  119. package/dist/src/commands/verify.js +3 -9
  120. package/dist/src/commands/watch.js +5 -1
  121. package/dist/src/commands/wire.js +1 -1
  122. package/dist/src/core/ast-utils.js +0 -1
  123. package/dist/src/core/brand-validation.js +0 -1
  124. package/dist/src/core/branding.js +0 -1
  125. package/dist/src/core/browser-wire.js +0 -1
  126. package/dist/src/core/build-audit-platform.js +0 -1
  127. package/dist/src/core/build-audit-registration.js +0 -1
  128. package/dist/src/core/build-audit-resolve.js +0 -1
  129. package/dist/src/core/build-audit-transforms.js +0 -1
  130. package/dist/src/core/build-audit.js +0 -1
  131. package/dist/src/core/build-baseline-types.d.ts +24 -0
  132. package/dist/src/core/build-baseline-types.js +0 -1
  133. package/dist/src/core/build-baseline.d.ts +5 -2
  134. package/dist/src/core/build-baseline.js +5 -2
  135. package/dist/src/core/build-prepare.js +2 -2
  136. package/dist/src/core/config-mutate.js +0 -1
  137. package/dist/src/core/config-paths.d.ts +2 -2
  138. package/dist/src/core/config-paths.js +6 -1
  139. package/dist/src/core/config-state.js +0 -1
  140. package/dist/src/core/config-validate-patch-policy.js +0 -1
  141. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  142. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  143. package/dist/src/core/config-validate.js +3 -1
  144. package/dist/src/core/config.js +0 -1
  145. package/dist/src/core/destructive.js +0 -1
  146. package/dist/src/core/diff-hunks.js +13 -7
  147. package/dist/src/core/engine-changes.js +0 -1
  148. package/dist/src/core/engine-session-lock.d.ts +12 -0
  149. package/dist/src/core/engine-session-lock.js +45 -0
  150. package/dist/src/core/file-lock.d.ts +8 -0
  151. package/dist/src/core/file-lock.js +157 -48
  152. package/dist/src/core/firefox-archive.d.ts +4 -0
  153. package/dist/src/core/firefox-archive.js +6 -1
  154. package/dist/src/core/firefox-cache.js +60 -4
  155. package/dist/src/core/firefox-download.js +0 -1
  156. package/dist/src/core/firefox-extract.js +75 -16
  157. package/dist/src/core/firefox-ignorefile.js +0 -1
  158. package/dist/src/core/firefox.d.ts +12 -0
  159. package/dist/src/core/firefox.js +64 -2
  160. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  161. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  162. package/dist/src/core/furnace-apply-ftl.js +10 -5
  163. package/dist/src/core/furnace-apply-helpers.js +0 -1
  164. package/dist/src/core/furnace-apply-output.js +2 -2
  165. package/dist/src/core/furnace-apply.d.ts +7 -1
  166. package/dist/src/core/furnace-apply.js +47 -13
  167. package/dist/src/core/furnace-checksum-utils.js +0 -1
  168. package/dist/src/core/furnace-config-array-utils.js +0 -1
  169. package/dist/src/core/furnace-config-custom.js +10 -1
  170. package/dist/src/core/furnace-config-order.js +0 -1
  171. package/dist/src/core/furnace-config-tokens.js +0 -1
  172. package/dist/src/core/furnace-config-validate.js +0 -1
  173. package/dist/src/core/furnace-config.d.ts +11 -0
  174. package/dist/src/core/furnace-config.js +37 -1
  175. package/dist/src/core/furnace-constants.js +0 -1
  176. package/dist/src/core/furnace-css-fragments.js +0 -1
  177. package/dist/src/core/furnace-graph-utils.js +0 -1
  178. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  179. package/dist/src/core/furnace-jsconfig.js +25 -18
  180. package/dist/src/core/furnace-marker.js +0 -1
  181. package/dist/src/core/furnace-operation.js +0 -1
  182. package/dist/src/core/furnace-refresh.js +0 -1
  183. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  184. package/dist/src/core/furnace-registration-ast.js +1 -2
  185. package/dist/src/core/furnace-registration-remove.js +0 -1
  186. package/dist/src/core/furnace-registration-validate.js +1 -5
  187. package/dist/src/core/furnace-registration.js +0 -1
  188. package/dist/src/core/furnace-rollback.js +0 -1
  189. package/dist/src/core/furnace-scanner.js +0 -1
  190. package/dist/src/core/furnace-stale-export.d.ts +59 -0
  191. package/dist/src/core/furnace-stale-export.js +123 -0
  192. package/dist/src/core/furnace-staleness.js +0 -1
  193. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  194. package/dist/src/core/furnace-state-persist.js +118 -0
  195. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  196. package/dist/src/core/furnace-step-errors.js +25 -0
  197. package/dist/src/core/furnace-stories.js +0 -1
  198. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  199. package/dist/src/core/furnace-validate-checks.js +0 -1
  200. package/dist/src/core/furnace-validate-compatibility.js +9 -3
  201. package/dist/src/core/furnace-validate-helpers.js +0 -1
  202. package/dist/src/core/furnace-validate-registration.js +7 -7
  203. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  204. package/dist/src/core/furnace-validate-structure.js +14 -4
  205. package/dist/src/core/furnace-validate.js +0 -1
  206. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  207. package/dist/src/core/furnace-version-drift.js +6 -4
  208. package/dist/src/core/git-base.js +0 -1
  209. package/dist/src/core/git-diff.js +0 -1
  210. package/dist/src/core/git-file-ops.js +0 -1
  211. package/dist/src/core/git-status.d.ts +13 -0
  212. package/dist/src/core/git-status.js +32 -2
  213. package/dist/src/core/git.d.ts +10 -1
  214. package/dist/src/core/git.js +63 -6
  215. package/dist/src/core/license-headers.js +0 -1
  216. package/dist/src/core/lint-cache.js +0 -1
  217. package/dist/src/core/lint-projection.js +0 -1
  218. package/dist/src/core/mach-build-artifacts.js +0 -1
  219. package/dist/src/core/mach-error-hints.js +0 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +60 -0
  221. package/dist/src/core/mach-known-noise-filter.js +193 -0
  222. package/dist/src/core/mach-mozconfig.js +0 -1
  223. package/dist/src/core/mach-python.js +0 -1
  224. package/dist/src/core/mach-resource-shim.js +0 -1
  225. package/dist/src/core/mach.d.ts +8 -0
  226. package/dist/src/core/mach.js +36 -4
  227. package/dist/src/core/manifest-helpers.js +0 -1
  228. package/dist/src/core/manifest-register.js +0 -1
  229. package/dist/src/core/manifest-rules.js +0 -1
  230. package/dist/src/core/manifest-tokenizers.js +0 -1
  231. package/dist/src/core/marionette-port.d.ts +8 -0
  232. package/dist/src/core/marionette-port.js +34 -3
  233. package/dist/src/core/marionette-preflight.js +0 -1
  234. package/dist/src/core/markdown-table.js +0 -1
  235. package/dist/src/core/ownership-table.js +2 -9
  236. package/dist/src/core/parser-fallback.js +0 -1
  237. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  238. package/dist/src/core/patch-apply-fuzz.js +71 -34
  239. package/dist/src/core/patch-apply.d.ts +30 -0
  240. package/dist/src/core/patch-apply.js +101 -18
  241. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  242. package/dist/src/core/patch-artifact-normalize.js +19 -15
  243. package/dist/src/core/patch-export-coverage.js +0 -1
  244. package/dist/src/core/patch-export-metadata.js +0 -1
  245. package/dist/src/core/patch-export-update.js +2 -3
  246. package/dist/src/core/patch-export.d.ts +2 -6
  247. package/dist/src/core/patch-export.js +24 -48
  248. package/dist/src/core/patch-files.js +0 -1
  249. package/dist/src/core/patch-identifier-suggest.js +0 -1
  250. package/dist/src/core/patch-lint-checkjs.js +0 -1
  251. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-cross.js +80 -3
  253. package/dist/src/core/patch-lint-css.js +0 -1
  254. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  255. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  256. package/dist/src/core/patch-lint-diff.js +17 -32
  257. package/dist/src/core/patch-lint-imports.js +0 -1
  258. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  259. package/dist/src/core/patch-lint-observer.js +0 -1
  260. package/dist/src/core/patch-lint-ownership.js +0 -1
  261. package/dist/src/core/patch-lint-reexports.js +0 -1
  262. package/dist/src/core/patch-lint.d.ts +37 -2
  263. package/dist/src/core/patch-lint.js +47 -34
  264. package/dist/src/core/patch-lock.js +0 -1
  265. package/dist/src/core/patch-manifest-consistency.js +9 -1
  266. package/dist/src/core/patch-manifest-io.d.ts +19 -2
  267. package/dist/src/core/patch-manifest-io.js +57 -21
  268. package/dist/src/core/patch-manifest-query.js +18 -10
  269. package/dist/src/core/patch-manifest-resolve.js +0 -1
  270. package/dist/src/core/patch-manifest-validate.js +24 -1
  271. package/dist/src/core/patch-manifest.d.ts +1 -1
  272. package/dist/src/core/patch-manifest.js +1 -2
  273. package/dist/src/core/patch-parse.d.ts +59 -0
  274. package/dist/src/core/patch-parse.js +209 -92
  275. package/dist/src/core/patch-policy.js +0 -1
  276. package/dist/src/core/patch-registration-refs.js +16 -26
  277. package/dist/src/core/patch-source-metadata.js +0 -1
  278. package/dist/src/core/patch-transform.d.ts +4 -0
  279. package/dist/src/core/patch-transform.js +30 -38
  280. package/dist/src/core/rebase-session.js +0 -1
  281. package/dist/src/core/register-browser-content.js +0 -1
  282. package/dist/src/core/register-module.js +0 -1
  283. package/dist/src/core/register-result.js +0 -1
  284. package/dist/src/core/register-scaffold.js +0 -1
  285. package/dist/src/core/register-shared-css.js +0 -1
  286. package/dist/src/core/register-test-manifest.js +0 -1
  287. package/dist/src/core/register-xpcshell-test.js +0 -1
  288. package/dist/src/core/shared-ftl.js +0 -1
  289. package/dist/src/core/signal-critical.js +0 -1
  290. package/dist/src/core/smoke-patterns.js +0 -1
  291. package/dist/src/core/state-file.js +0 -1
  292. package/dist/src/core/status-classify.d.ts +16 -0
  293. package/dist/src/core/status-classify.js +24 -16
  294. package/dist/src/core/test-harness-crash.d.ts +80 -7
  295. package/dist/src/core/test-harness-crash.js +251 -15
  296. package/dist/src/core/test-harness-output.js +0 -1
  297. package/dist/src/core/test-path-scope.d.ts +21 -13
  298. package/dist/src/core/test-path-scope.js +43 -28
  299. package/dist/src/core/test-stale-check.d.ts +29 -1
  300. package/dist/src/core/test-stale-check.js +59 -1
  301. package/dist/src/core/test-stale-symlink.js +0 -1
  302. package/dist/src/core/test-xpcshell-retry.js +0 -1
  303. package/dist/src/core/token-coverage.js +0 -1
  304. package/dist/src/core/token-dark-mode.js +0 -1
  305. package/dist/src/core/token-docs.js +0 -1
  306. package/dist/src/core/token-manager.js +0 -1
  307. package/dist/src/core/token-scaffold.js +0 -1
  308. package/dist/src/core/token-variant.js +0 -1
  309. package/dist/src/core/toolchain-preflight.d.ts +31 -10
  310. package/dist/src/core/toolchain-preflight.js +97 -36
  311. package/dist/src/core/typecheck-shim.js +13 -1
  312. package/dist/src/core/typecheck.js +0 -1
  313. package/dist/src/core/wire-destroy.js +0 -1
  314. package/dist/src/core/wire-dom-fragment.js +0 -1
  315. package/dist/src/core/wire-init.js +0 -1
  316. package/dist/src/core/wire-subscript.js +0 -1
  317. package/dist/src/core/wire-targets.js +0 -1
  318. package/dist/src/core/wire-utils.js +0 -1
  319. package/dist/src/core/xpcshell-appdir.js +1 -4
  320. package/dist/src/errors/base.d.ts +29 -0
  321. package/dist/src/errors/base.js +35 -1
  322. package/dist/src/errors/build.js +0 -1
  323. package/dist/src/errors/codes.d.ts +6 -0
  324. package/dist/src/errors/codes.js +6 -1
  325. package/dist/src/errors/config.js +0 -1
  326. package/dist/src/errors/download.js +0 -1
  327. package/dist/src/errors/furnace.d.ts +11 -1
  328. package/dist/src/errors/furnace.js +6 -5
  329. package/dist/src/errors/git.js +0 -1
  330. package/dist/src/errors/patch.d.ts +17 -0
  331. package/dist/src/errors/patch.js +29 -1
  332. package/dist/src/errors/rebase.js +0 -1
  333. package/dist/src/errors/run.js +1 -1
  334. package/dist/src/index.js +0 -1
  335. package/dist/src/types/cli.js +0 -1
  336. package/dist/src/types/commands/index.d.ts +1 -1
  337. package/dist/src/types/commands/index.js +0 -1
  338. package/dist/src/types/commands/options.d.ts +40 -9
  339. package/dist/src/types/commands/options.js +0 -1
  340. package/dist/src/types/commands/patches.d.ts +37 -0
  341. package/dist/src/types/commands/patches.js +0 -1
  342. package/dist/src/types/commands/project.d.ts +11 -2
  343. package/dist/src/types/commands/project.js +0 -1
  344. package/dist/src/types/config.d.ts +32 -0
  345. package/dist/src/types/config.js +0 -1
  346. package/dist/src/types/furnace.d.ts +23 -0
  347. package/dist/src/types/furnace.js +0 -1
  348. package/dist/src/types/index.js +0 -1
  349. package/dist/src/types/typecheck.js +0 -1
  350. package/dist/src/utils/elapsed.js +0 -1
  351. package/dist/src/utils/errors.js +0 -1
  352. package/dist/src/utils/fs.js +37 -5
  353. package/dist/src/utils/logger.d.ts +7 -0
  354. package/dist/src/utils/logger.js +85 -3
  355. package/dist/src/utils/options.d.ts +15 -0
  356. package/dist/src/utils/options.js +25 -1
  357. package/dist/src/utils/package-root.js +11 -4
  358. package/dist/src/utils/parse.js +0 -1
  359. package/dist/src/utils/paths.js +0 -1
  360. package/dist/src/utils/platform.js +0 -1
  361. package/dist/src/utils/process-group.d.ts +33 -0
  362. package/dist/src/utils/process-group.js +161 -0
  363. package/dist/src/utils/process.d.ts +48 -4
  364. package/dist/src/utils/process.js +258 -59
  365. package/dist/src/utils/regex.d.ts +13 -3
  366. package/dist/src/utils/regex.js +53 -4
  367. package/dist/src/utils/validation.d.ts +0 -12
  368. package/dist/src/utils/validation.js +13 -26
  369. package/package.json +9 -8
@@ -13,7 +13,29 @@ import { verbose } from '../utils/logger.js';
13
13
  import { isBrandingManagedPath } from './branding.js';
14
14
  import { computePatchedContent } from './patch-apply.js';
15
15
  import { loadPatchesManifest } from './patch-manifest.js';
16
- function getPrimaryStatusCode(status) {
16
+ /**
17
+ * Builds the file → owning-patch-filenames multimap from manifest rows.
18
+ * Single source of truth for cross-patch claim detection — status
19
+ * classification, verify's cross-claim check, and the ownership table all
20
+ * consume this builder (they used to each rebuild it, one drift away from
21
+ * disagreeing about what "claimed" means).
22
+ */
23
+ export function buildPatchClaims(manifestPatches) {
24
+ const ownersByPath = new Map();
25
+ for (const patch of manifestPatches) {
26
+ for (const file of patch.filesAffected) {
27
+ const existing = ownersByPath.get(file) ?? [];
28
+ existing.push(patch.filename);
29
+ ownersByPath.set(file, existing);
30
+ }
31
+ }
32
+ return ownersByPath;
33
+ }
34
+ /**
35
+ * Reduces a two-character porcelain XY status to its primary code.
36
+ * Single source of truth — status-output.ts renders from the same logic.
37
+ */
38
+ export function getPrimaryStatusCode(status) {
17
39
  if (status.includes('?'))
18
40
  return '?';
19
41
  if (status.includes('!'))
@@ -55,20 +77,7 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
55
77
  // branch where the expected-vs-actual content comparison then routed
56
78
  // them into `unmanaged` when the content didn't match either owner's
57
79
  // expectation.
58
- const patchClaims = new Map();
59
- if (manifest) {
60
- for (const patch of manifest.patches) {
61
- for (const f of patch.filesAffected) {
62
- const owners = patchClaims.get(f);
63
- if (owners) {
64
- owners.push(patch.filename);
65
- }
66
- else {
67
- patchClaims.set(f, [patch.filename]);
68
- }
69
- }
70
- }
71
- }
80
+ const patchClaims = manifest ? buildPatchClaims(manifest.patches) : new Map();
72
81
  const results = [];
73
82
  for (const entry of files) {
74
83
  const owners = patchClaims.get(entry.file);
@@ -145,4 +154,3 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
145
154
  }
146
155
  return results;
147
156
  }
148
- //# sourceMappingURL=status-classify.js.map
@@ -32,12 +32,42 @@ export interface HarnessCrashSignature {
32
32
  export interface HarnessRunVerdict {
33
33
  kind: HarnessRunClassification;
34
34
  signature?: HarnessCrashSignature;
35
+ /** First concrete test-failure evidence line, when available. */
36
+ realFailureLine?: string;
37
+ /**
38
+ * First N `TEST-UNEXPECTED-*` lines verbatim, each with its trailing
39
+ * assertion/diff context lines (`Got …` / `Expected …` / Assert diff /
40
+ * stack head). Lets the failure summary echo the actual assertion so a
41
+ * one-off failure that does not reproduce is still diagnosable after the
42
+ * fact. Only set on `test-failures` verdicts when such lines exist.
43
+ */
44
+ realFailureBlocks?: string[];
45
+ /** Harness noise seen in the same output as a real test failure. */
46
+ secondaryHarnessSignature?: HarnessCrashSignature;
35
47
  /**
36
48
  * Set when a non-zero mach exit code was overridden because the output
37
49
  * embeds a completed green summary — the exit code followed harness
38
50
  * noise, not a test result. Callers should surface a note.
39
51
  */
40
52
  greenSummaryOverride?: boolean;
53
+ /**
54
+ * Set when the output embeds a green-LOOKING summary that was REJECTED:
55
+ * crash or truncation evidence proves the suite never completed, so the
56
+ * "green" counts only cover the files that ran before the run died
57
+ * (field report: a SIGSEGV at the second of eight files left a
58
+ * `Passed: 2 / Failed: 0` summary that 0.35.0 green-washed into a pass).
59
+ * Callers must fail the run and surface the evidence.
60
+ */
61
+ greenSummaryRejected?: GreenSummaryRejection;
62
+ }
63
+ /** Why a green-looking embedded summary was not trusted. */
64
+ export interface GreenSummaryRejection {
65
+ /** Crash-marker evidence line (mozcrash / signal kill), if any. */
66
+ crashLine?: string;
67
+ /** Requested test files with no `TEST_START`/`TEST-START` line at all. */
68
+ neverStarted: string[];
69
+ /** Files whose `TEST_START` has no matching end marker. */
70
+ neverEnded: string[];
41
71
  }
42
72
  /**
43
73
  * Strips non-signal noise from captured output before crash-signature
@@ -47,17 +77,49 @@ export interface HarnessRunVerdict {
47
77
  * classification of test results still reads the full output.
48
78
  */
49
79
  export declare function stripNonSignalNoise(output: string): string;
80
+ /**
81
+ * Finds the first crash-marker line in the RAW output (see
82
+ * {@link CRASH_MARKER_PATTERNS}), or undefined when the run shows no
83
+ * crash evidence. Exported for direct unit testing.
84
+ */
85
+ export declare function findCrashMarkerLine(output: string): string | undefined;
50
86
  /**
51
87
  * True when the output embeds a COMPLETED, GREEN suite summary: an
52
88
  * execution signal, `Unexpected results: 0` (and no non-zero unexpected
53
- * count), a `SUITE_END` marker, and no real `TEST-UNEXPECTED-*` lines.
54
- * Such a run finished its suite; any startup-traceback-shaped noise in the
55
- * same output is by definition non-fatal, so this vetoes signature-based
56
- * crash classification (field report: fully green sharded runs reported
57
- * `CRASH (N attempts)` because degradation warnings matched the psutil
58
- * signals). Exported for direct unit testing.
89
+ * count), a `SUITE_END` marker, no real `TEST-UNEXPECTED-*` lines — and
90
+ * NO crash marker. Such a run finished its suite; any startup-traceback-
91
+ * shaped noise in the same output is by definition non-fatal, so this
92
+ * vetoes signature-based crash classification (field report: fully green
93
+ * sharded runs reported `CRASH (N attempts)` because degradation warnings
94
+ * matched the psutil signals). A summary printed after a crash marker is
95
+ * NOT "completed" — it only covers the files that ran before the crash
96
+ * (0.35.0 green-wash field report). Exported for direct unit testing.
59
97
  */
60
98
  export declare function hasCompletedGreenSummary(output: string): boolean;
99
+ /**
100
+ * Cross-checks run completeness against the requested test files: every
101
+ * requested file must produce a start marker, and every started test must
102
+ * produce an end marker (the harness emits both even for failing files).
103
+ *
104
+ * Ends are paired with starts POSITIONALLY (last open start is closed by
105
+ * the next end), not by filename — the xpcshell dialect's `TEST_END: Test
106
+ * PASS` lines do not name the file. Requested paths that are not
107
+ * test-file-shaped (directories, manifests) are skipped for the
108
+ * never-started check; the start/end pairing scan covers them regardless.
109
+ * Exported for direct unit testing.
110
+ */
111
+ export declare function analyzeTestCompleteness(output: string, requestedPaths: readonly string[]): {
112
+ neverStarted: string[];
113
+ neverEnded: string[];
114
+ };
115
+ /**
116
+ * Collects the first `limit` TEST-UNEXPECTED-* lines with their trailing
117
+ * assertion/diff context, one string per block. The shutdown-reentry
118
+ * artifact is excluded (it is harness noise, not a test result). When more
119
+ * than `limit` blocks exist, a final `…(+N more …)` note is appended so the
120
+ * operator knows the echo was truncated. Pure; exported for unit tests.
121
+ */
122
+ export declare function collectUnexpectedFailureBlocks(output: string, limit?: number): string[];
61
123
  /**
62
124
  * Detects the known harness-crash shapes in captured mach output.
63
125
  * Returns undefined for anything that looks like a genuine test result.
@@ -76,9 +138,20 @@ export declare function detectHarnessCrashSignature(output: string): HarnessCras
76
138
  * dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
77
139
  * lines are still not trusted as evidence of execution.
78
140
  * 3. Exit code zero with tests started is a pass; anything else is a
79
- * test failure for the regular diagnosis chain.
141
+ * test failure for the regular diagnosis chain — except a non-zero
142
+ * exit whose embedded summary completed green AND shows no crash
143
+ * marker and no truncated/never-started requested file, which is a
144
+ * pass with a `greenSummaryOverride` note (harness noise owns the
145
+ * exit code). A green-shaped summary WITH such evidence is rejected
146
+ * (`greenSummaryRejected`) and fails.
80
147
  */
81
148
  export declare function classifyHarnessRun(exitCode: number, output: string, requestedPaths: readonly string[]): HarnessRunVerdict;
149
+ /**
150
+ * Builds the operator-facing failure message for a green-looking summary
151
+ * that was rejected on crash or truncation evidence (never trusted as a
152
+ * pass, regardless of how green the embedded counts look).
153
+ */
154
+ export declare function buildGreenSummaryRejectedMessage(rejection: GreenSummaryRejection, exitCode: number): string;
82
155
  /** Builds the operator-facing failure message after retries are exhausted. */
83
156
  export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
84
157
  /**
@@ -47,10 +47,30 @@ const NONZERO_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*[1-9]/;
47
47
  */
48
48
  const XPCSHELL_RESULT_SUMMARY_PATTERN = /\bTEST_END\b|\bRan \d+ checks?\b|\bResult summary:/i;
49
49
  const UNEXPECTED_LINE_PATTERN = /^.*\bTEST-UNEXPECTED-[A-Z-]+\b.*$/gm;
50
+ const FAIL_LINE_PATTERN = /^.*\bFAIL\b.*$/gm;
51
+ const ASSERTION_LINE_PATTERN = /^.*\b(?:Assertion failure|MOZ_ASSERT|ASSERTION)\b.*$/gim;
50
52
  const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
51
53
  const FOCUS_STALL_PATTERN = /must wait for focus/i;
52
54
  const TRACEBACK_PATTERN = /Traceback \(most recent call last\)/;
53
55
  const NO_OUTPUT_TIMEOUT_PATTERN = /timed out after \d+ seconds with no output/i;
56
+ /**
57
+ * Crash-marker lines mozcrash / the mochitest harness print when the
58
+ * browser process itself died. Matched against the RAW output (never
59
+ * noise-stripped): any of these proves the run was truncated by a crash,
60
+ * so an embedded green summary is under-reporting, not a result (field
61
+ * report: `Main app process: killed by SIGSEGV` at the second of eight
62
+ * files, summary `Passed: 2 / Failed: 0`, mach exit 1 — 0.35.0 accepted
63
+ * it as passed and masked a real regression for a whole drill).
64
+ */
65
+ const CRASH_MARKER_PATTERNS = [
66
+ /Main app process: killed by SIG\w+/,
67
+ /\bPROCESS-CRASH\b/,
68
+ // The minidump-processing header shapes mozcrash emits when it walks a
69
+ // crash dump after the run.
70
+ /mozcrash.*minidump/i,
71
+ /Crash dump filename/i,
72
+ /Thread \d+ \(crashed\)/,
73
+ ];
54
74
  /**
55
75
  * Startup-traceback fingerprints from the mozlog resource monitor / psutil
56
76
  * on macOS. Each is matched per-line so the evidence line in the report is
@@ -165,22 +185,89 @@ function hasExecutionSignal(output) {
165
185
  hasXpcshellResultSummary(output));
166
186
  }
167
187
  /**
168
- * True when the output embeds a COMPLETED, GREEN suite summary: an
169
- * execution signal, `Unexpected results: 0` (and no non-zero unexpected
170
- * count), a `SUITE_END` marker, and no real `TEST-UNEXPECTED-*` lines.
171
- * Such a run finished its suite; any startup-traceback-shaped noise in the
172
- * same output is by definition non-fatal, so this vetoes signature-based
173
- * crash classification (field report: fully green sharded runs reported
174
- * `CRASH (N attempts)` because degradation warnings matched the psutil
175
- * signals). Exported for direct unit testing.
188
+ * Finds the first crash-marker line in the RAW output (see
189
+ * {@link CRASH_MARKER_PATTERNS}), or undefined when the run shows no
190
+ * crash evidence. Exported for direct unit testing.
176
191
  */
177
- export function hasCompletedGreenSummary(output) {
192
+ export function findCrashMarkerLine(output) {
193
+ return findLine(output, CRASH_MARKER_PATTERNS);
194
+ }
195
+ /**
196
+ * True when the summary lines LOOK green: execution signal, `Unexpected
197
+ * results: 0` with no non-zero count, `SUITE_END`, no real
198
+ * `TEST-UNEXPECTED-*` lines. Deliberately blind to crash/truncation
199
+ * evidence — {@link hasCompletedGreenSummary} layers that on top, and
200
+ * {@link classifyHarnessRun} needs the distinction to report WHY a
201
+ * green-shaped summary was rejected.
202
+ */
203
+ function hasGreenShapedSummary(output) {
178
204
  return (hasExecutionSignal(output) &&
179
205
  SUITE_END_PATTERN.test(output) &&
180
206
  GREEN_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
181
207
  !NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
182
208
  realUnexpectedFailureLines(output).length === 0);
183
209
  }
210
+ /**
211
+ * True when the output embeds a COMPLETED, GREEN suite summary: an
212
+ * execution signal, `Unexpected results: 0` (and no non-zero unexpected
213
+ * count), a `SUITE_END` marker, no real `TEST-UNEXPECTED-*` lines — and
214
+ * NO crash marker. Such a run finished its suite; any startup-traceback-
215
+ * shaped noise in the same output is by definition non-fatal, so this
216
+ * vetoes signature-based crash classification (field report: fully green
217
+ * sharded runs reported `CRASH (N attempts)` because degradation warnings
218
+ * matched the psutil signals). A summary printed after a crash marker is
219
+ * NOT "completed" — it only covers the files that ran before the crash
220
+ * (0.35.0 green-wash field report). Exported for direct unit testing.
221
+ */
222
+ export function hasCompletedGreenSummary(output) {
223
+ return hasGreenShapedSummary(output) && findCrashMarkerLine(output) === undefined;
224
+ }
225
+ /** Basename shape of a mozbuild-manifest test implementation file. */
226
+ const TEST_FILE_BASENAME_PATTERN = /^(?:browser|test)_.*\.m?js$/;
227
+ /**
228
+ * Start/end execution markers, both log dialects: the structured mozlog
229
+ * `TEST_START`/`TEST_END` pair and the human browser-chrome
230
+ * `TEST-START`/`TEST-OK` pair. `TEST-UNEXPECTED-*` end shapes are
231
+ * irrelevant here — any such line already fails the green-summary check
232
+ * before completeness is consulted.
233
+ */
234
+ const START_MARKER_PATTERN = /\bTEST[-_]START\b[:| ]*\s*(\S+)?/;
235
+ const END_MARKER_PATTERN = /\bTEST[-_]END\b|\bTEST-OK\b/;
236
+ /**
237
+ * Cross-checks run completeness against the requested test files: every
238
+ * requested file must produce a start marker, and every started test must
239
+ * produce an end marker (the harness emits both even for failing files).
240
+ *
241
+ * Ends are paired with starts POSITIONALLY (last open start is closed by
242
+ * the next end), not by filename — the xpcshell dialect's `TEST_END: Test
243
+ * PASS` lines do not name the file. Requested paths that are not
244
+ * test-file-shaped (directories, manifests) are skipped for the
245
+ * never-started check; the start/end pairing scan covers them regardless.
246
+ * Exported for direct unit testing.
247
+ */
248
+ export function analyzeTestCompleteness(output, requestedPaths) {
249
+ const startedNames = [];
250
+ const openTests = [];
251
+ for (const line of output.split(/\r?\n/)) {
252
+ const startMatch = START_MARKER_PATTERN.exec(line);
253
+ if (startMatch) {
254
+ const name = startMatch[1] ?? '(unnamed test)';
255
+ startedNames.push(name);
256
+ openTests.push(name);
257
+ continue;
258
+ }
259
+ if (END_MARKER_PATTERN.test(line)) {
260
+ openTests.pop();
261
+ }
262
+ }
263
+ const neverStarted = requestedPaths.filter((path) => {
264
+ const base = path.slice(path.lastIndexOf('/') + 1);
265
+ if (!TEST_FILE_BASENAME_PATTERN.test(base))
266
+ return false;
267
+ return !startedNames.some((name) => name.includes(base));
268
+ });
269
+ return { neverStarted, neverEnded: openTests };
270
+ }
184
271
  /**
185
272
  * True when the captured output carries the suite-specific xpcshell
186
273
  * result-summary block, which proves tests executed even though the
@@ -193,14 +280,101 @@ function hasXpcshellResultSummary(output) {
193
280
  }
194
281
  /** Unexpected-failure lines that are NOT the shutdown re-entry artifact. */
195
282
  function realUnexpectedFailureLines(output) {
196
- const matches = output.match(UNEXPECTED_LINE_PATTERN) ?? [];
197
- return matches.filter((line) => !SHUTDOWN_REENTRY_PATTERN.test(line));
283
+ const matches = [
284
+ ...(output.match(UNEXPECTED_LINE_PATTERN) ?? []),
285
+ ...(output.match(FAIL_LINE_PATTERN) ?? []),
286
+ ...(output.match(ASSERTION_LINE_PATTERN) ?? []),
287
+ ];
288
+ if (NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output)) {
289
+ const summary = output
290
+ .split(/\r?\n/)
291
+ .find((line) => NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(line));
292
+ if (summary)
293
+ matches.push(summary);
294
+ }
295
+ return [...new Set(matches.map((line) => line.trim()))].filter((line) => !SHUTDOWN_REENTRY_PATTERN.test(line));
296
+ }
297
+ /**
298
+ * Non-global copies of the unexpected/assertion patterns for per-line use —
299
+ * the `g`-flagged module patterns carry `lastIndex` state across `.test()`
300
+ * calls and must never be reused line-by-line.
301
+ */
302
+ const UNEXPECTED_LINE_SINGLE = /\bTEST-UNEXPECTED-[A-Z-]+\b/;
303
+ /**
304
+ * Context lines the chrome/xpcshell harnesses print directly under a
305
+ * TEST-UNEXPECTED line: `Got …` / `Expected …` / `Actual …`, Assert
306
+ * messages, diff markers, stack-trace heads, and indented continuation
307
+ * lines.
308
+ */
309
+ const FAILURE_CONTEXT_LINE_PATTERN = /^(?:\s*(?:Got\b|Expected\b|Actual\b|Assert\w*\b|Stack trace:|[-+]\s)|\s{2,}\S)/;
310
+ /** Cap on context lines echoed under a single TEST-UNEXPECTED line. */
311
+ const FAILURE_BLOCK_CONTEXT_LIMIT = 6;
312
+ /**
313
+ * Collects the first `limit` TEST-UNEXPECTED-* lines with their trailing
314
+ * assertion/diff context, one string per block. The shutdown-reentry
315
+ * artifact is excluded (it is harness noise, not a test result). When more
316
+ * than `limit` blocks exist, a final `…(+N more …)` note is appended so the
317
+ * operator knows the echo was truncated. Pure; exported for unit tests.
318
+ */
319
+ export function collectUnexpectedFailureBlocks(output, limit = 5) {
320
+ const lines = output.split(/\r?\n/);
321
+ const blocks = [];
322
+ let skipped = 0;
323
+ for (let i = 0; i < lines.length; i++) {
324
+ const line = lines[i] ?? '';
325
+ if (!UNEXPECTED_LINE_SINGLE.test(line) || SHUTDOWN_REENTRY_PATTERN.test(line))
326
+ continue;
327
+ if (blocks.length >= limit) {
328
+ skipped++;
329
+ continue;
330
+ }
331
+ const block = [line.trim()];
332
+ let contextCount = 0;
333
+ for (let j = i + 1; j < lines.length && contextCount < FAILURE_BLOCK_CONTEXT_LIMIT; j++) {
334
+ const next = lines[j] ?? '';
335
+ if (UNEXPECTED_LINE_SINGLE.test(next))
336
+ break;
337
+ if (!FAILURE_CONTEXT_LINE_PATTERN.test(next))
338
+ break;
339
+ block.push(next.trimEnd());
340
+ contextCount++;
341
+ }
342
+ blocks.push(block.join('\n'));
343
+ }
344
+ if (skipped > 0) {
345
+ blocks.push(`…(+${skipped} more TEST-UNEXPECTED line${skipped === 1 ? '' : 's'} not shown)`);
346
+ }
347
+ return blocks;
348
+ }
349
+ function detectSecondaryHarnessNoise(output) {
350
+ const evidence = stripNonSignalNoise(output);
351
+ if (TRACEBACK_PATTERN.test(evidence)) {
352
+ const signalLine = findLine(evidence, STARTUP_TRACEBACK_SIGNALS);
353
+ if (signalLine) {
354
+ return {
355
+ reason: 'harness traceback also present (resource monitor/psutil)',
356
+ line: signalLine,
357
+ };
358
+ }
359
+ }
360
+ const timeoutLine = findLine(evidence, [NO_OUTPUT_TIMEOUT_PATTERN]);
361
+ if (timeoutLine) {
362
+ return { reason: 'harness no-output timeout also present', line: timeoutLine };
363
+ }
364
+ return undefined;
198
365
  }
199
366
  /**
200
367
  * Detects the known harness-crash shapes in captured mach output.
201
368
  * Returns undefined for anything that looks like a genuine test result.
202
369
  */
203
370
  export function detectHarnessCrashSignature(output) {
371
+ // Browser/app process crashes are deterministic crash evidence, not
372
+ // retriable environmental harness noise. Leave them to
373
+ // `classifyHarnessRun` so it can reject green-looking truncated summaries
374
+ // with the crash line plus never-started/never-ended file evidence.
375
+ if (findCrashMarkerLine(output) !== undefined) {
376
+ return undefined;
377
+ }
204
378
  const hasTestStart = hasExecutionSignal(output);
205
379
  const realFailures = realUnexpectedFailureLines(output);
206
380
  // A completed green embedded summary vetoes signature-based crash
@@ -265,9 +439,18 @@ export function detectHarnessCrashSignature(output) {
265
439
  * dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
266
440
  * lines are still not trusted as evidence of execution.
267
441
  * 3. Exit code zero with tests started is a pass; anything else is a
268
- * test failure for the regular diagnosis chain.
442
+ * test failure for the regular diagnosis chain — except a non-zero
443
+ * exit whose embedded summary completed green AND shows no crash
444
+ * marker and no truncated/never-started requested file, which is a
445
+ * pass with a `greenSummaryOverride` note (harness noise owns the
446
+ * exit code). A green-shaped summary WITH such evidence is rejected
447
+ * (`greenSummaryRejected`) and fails.
269
448
  */
270
449
  export function classifyHarnessRun(exitCode, output, requestedPaths) {
450
+ const realFailures = realUnexpectedFailureLines(output);
451
+ const firstRealFailure = realFailures[0];
452
+ const failureBlocks = collectUnexpectedFailureBlocks(output);
453
+ const secondaryHarnessSignature = firstRealFailure !== undefined ? detectSecondaryHarnessNoise(output) : undefined;
271
454
  const signature = detectHarnessCrashSignature(output);
272
455
  if (signature) {
273
456
  return { kind: 'harness-crash', signature };
@@ -284,10 +467,64 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
284
467
  // non-zero on harness noise (field report: a fully green --no-shard run
285
468
  // exited 1). Real failures always carry a non-zero unexpected count or
286
469
  // TEST-UNEXPECTED lines, both of which fail the green-summary check.
287
- if (hasCompletedGreenSummary(output)) {
470
+ //
471
+ // The override must never win over crash or truncation evidence
472
+ // (0.35.0 green-wash field report: a SIGSEGV at the second of eight
473
+ // files produced a "green" `Passed: 2 / Failed: 0` summary and mach
474
+ // exit 1 — the summary was green only because the crash prevented the
475
+ // other six files from producing any results). A crash marker or an
476
+ // unpaired/never-started requested file rejects the override with the
477
+ // evidence attached, so the caller can say why.
478
+ if (hasGreenShapedSummary(output)) {
479
+ const crashLine = findCrashMarkerLine(output);
480
+ const completeness = analyzeTestCompleteness(output, requestedPaths);
481
+ if (crashLine !== undefined ||
482
+ completeness.neverStarted.length > 0 ||
483
+ completeness.neverEnded.length > 0) {
484
+ return {
485
+ kind: 'test-failures',
486
+ ...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
487
+ ...(failureBlocks.length > 0 ? { realFailureBlocks: failureBlocks } : {}),
488
+ ...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
489
+ greenSummaryRejected: {
490
+ ...(crashLine !== undefined ? { crashLine } : {}),
491
+ neverStarted: completeness.neverStarted,
492
+ neverEnded: completeness.neverEnded,
493
+ },
494
+ };
495
+ }
288
496
  return { kind: 'tests-ran-ok', greenSummaryOverride: true };
289
497
  }
290
- return { kind: 'test-failures' };
498
+ return {
499
+ kind: 'test-failures',
500
+ ...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
501
+ ...(failureBlocks.length > 0 ? { realFailureBlocks: failureBlocks } : {}),
502
+ ...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
503
+ };
504
+ }
505
+ /**
506
+ * Builds the operator-facing failure message for a green-looking summary
507
+ * that was rejected on crash or truncation evidence (never trusted as a
508
+ * pass, regardless of how green the embedded counts look).
509
+ */
510
+ export function buildGreenSummaryRejectedMessage(rejection, exitCode) {
511
+ const evidence = [];
512
+ if (rejection.crashLine !== undefined) {
513
+ evidence.push(` - crash evidence: ${rejection.crashLine}`);
514
+ }
515
+ if (rejection.neverStarted.length > 0) {
516
+ evidence.push(` - requested test file(s) that never started: ${rejection.neverStarted.join(', ')}`);
517
+ }
518
+ if (rejection.neverEnded.length > 0) {
519
+ evidence.push(` - test file(s) that started but never finished: ${rejection.neverEnded.join(', ')}`);
520
+ }
521
+ return (`mach exited ${exitCode} and the embedded suite summary looks green, but FireForge did NOT ` +
522
+ 'treat the run as passed:\n\n' +
523
+ `${evidence.join('\n')}\n\n` +
524
+ 'A crash- or truncation-shortened run under-reports: the "green" counts only cover the ' +
525
+ 'files that ran before the run died, so the summary is not a suite result. Treat this as a ' +
526
+ 'FAILED run — check the crash/truncation point in the output above, then re-run the ' +
527
+ 'remaining files.');
291
528
  }
292
529
  /** Builds the operator-facing failure message after retries are exhausted. */
293
530
  export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
@@ -315,4 +552,3 @@ export function buildNoTestsRanMessage(exitCode, requestedPaths) {
315
552
  'Check that the paths are registered in their test manifest (browser.toml / xpcshell.toml) ' +
316
553
  'and that the manifest is reachable from moz.build, then retry.');
317
554
  }
318
- //# sourceMappingURL=test-harness-crash.js.map
@@ -86,4 +86,3 @@ export function buildHarnessEarlyExitMessage(earlyExit, normalizedPaths) {
86
86
  `Requested paths: ${paths}\n\n` +
87
87
  'Fix the harness startup/discovery issue above before interpreting this as a test failure.');
88
88
  }
89
- //# sourceMappingURL=test-harness-output.js.map
@@ -5,17 +5,21 @@
5
5
  * so `fireforge test browser/base/content/test/hominis` silently also
6
6
  * ran the sibling directory `browser/base/content/test/hominis-tiles`
7
7
  * (152.0b7 → 153.0b8 source-refresh drill: 1224 tests instead of ~200,
8
- * with no indication the scope widened). A trailing separator makes the
9
- * prefix match exact — `hominis/` cannot prefix `hominis-tiles/…`.
8
+ * with no indication the scope widened).
10
9
  *
11
10
  * FireForge therefore treats a directory argument as meaning EXACTLY
12
- * that directory: {@link analyzeTestPathScopes} returns a trailing-`/`
13
- * dispatch form for every existing directory, plus the sibling
14
- * directories the raw prefix would have swept in so the command layer
15
- * can tell the operator what was excluded. Note FireForge already
16
- * rejects non-existent paths up front, so raw prefix-widening was never
17
- * something an operator could invoke deliberately it only ever
18
- * happened by accident.
11
+ * that directory: {@link analyzeTestPathScopes} enumerates the test
12
+ * files of exactly that directory and dispatches THAT explicit file
13
+ * list to mach a file list cannot prefix-match anything. (0.35.0
14
+ * instead normalized the directory with a trailing `/`, assuming the
15
+ * separator makes the prefix match exact; field verification on Firefox
16
+ * 153 showed mach still swept in the prefix-named sibling, so the
17
+ * trailing-slash mechanism was cosmetic and its exclusion echo was
18
+ * wrong.) The prefix-matching sibling directories are still reported so
19
+ * the command layer can tell the operator what was excluded. Note
20
+ * FireForge already rejects non-existent paths up front, so raw
21
+ * prefix-widening was never something an operator could invoke
22
+ * deliberately — it only ever happened by accident.
19
23
  */
20
24
  /** A sibling directory the raw mach prefix match would have included. */
21
25
  export interface SiblingPrefixMatch {
@@ -29,13 +33,17 @@ export interface TestPathScope {
29
33
  /** The path as the operator passed it (engine-relative, normalized). */
30
34
  requestedPath: string;
31
35
  /**
32
- * The form to hand to mach: directories gain a trailing `/` so
33
- * mozbuild's prefix match is exact; files pass through unchanged.
36
+ * The paths to hand to mach for this argument: the explicit test-file
37
+ * list for a directory (mach cannot prefix-widen an explicit file
38
+ * list), or the argument unchanged for files. A directory containing
39
+ * no enumerable test files falls back to the trailing-`/` directory
40
+ * form — there is nothing to list, and mach owns the "no tests found"
41
+ * failure.
34
42
  */
35
- dispatchPath: string;
43
+ dispatchPaths: string[];
36
44
  /** True when the path resolved to a directory under engine/. */
37
45
  isDirectory: boolean;
38
- /** Recursive test-file count inside the requested directory (0 for files). */
46
+ /** Test files enumerated inside the requested directory (empty for files). */
39
47
  testFileCount: number;
40
48
  /** Sibling directories a raw prefix match would also have selected. */
41
49
  siblingPrefixMatches: SiblingPrefixMatch[];