@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
package/docs/testing.md CHANGED
@@ -19,6 +19,134 @@ FireForge announces this with a notice, since isolated instances do not
19
19
  exercise cross-argument state. `--no-shard` restores one combined
20
20
  invocation.
21
21
 
22
+ ## Several runs under one lock hold
23
+
24
+ Every `fireforge test` holds the engine-session lock for its whole run. Three
25
+ request shapes run more than one mach invocation inside that one hold, so a
26
+ peer cannot take the lock, build, or replace the packaging record between
27
+ them:
28
+
29
+ - **Mixed harnesses.** xpcshell and browser/mochitest paths in one request
30
+ run as two consecutive mach invocations, the xpcshell half first. With
31
+ `--build`, one pre-test build packages both halves (its coverage claim is
32
+ the union), and the second half runs build-less against it. Before 0.50.0
33
+ a mixed request was refused, and the documented split lost its coverage
34
+ claim to peers' scoped builds between the steps.
35
+ - **`--repeat <n>`** runs the same selection n times. A `--build` builds
36
+ once, and `--doctor` probes once. Every run is reported, so a flaky suite's
37
+ rate is visible from one invocation.
38
+ - **`--negative-control <diff>`**, below.
39
+
40
+ Each sub-run prints the verdict it would have printed alone as a
41
+ `FIREFORGE-RUN:` line (its own status, reason and counts, plus
42
+ `run=<i>/<n>` and, for a mixed request, `harness=xpcshell|mochitest`). The
43
+ invocation still ends with exactly one `FIREFORGE-VERDICT:` line:
44
+ `PASS runs=<n>/<n>`, or `FAIL reason=<most structural failing run>
45
+ [note=…] runs=<passed>/<planned>`. Runs continue after a red or a crash; a
46
+ preflight refusal or an inconclusive run stops the invocation, since the
47
+ runs after it would not be comparable. `--mach-arg` and every other option
48
+ apply to each sub-run. `--repeat` and `--negative-control` need explicit
49
+ paths and cannot be combined with `--build-only`, `--canary` or `--auto`.
50
+
51
+ ## Negative controls
52
+
53
+ A negative control proves a test can fail: neuter the code under test, run
54
+ the test, and expect red. Done by hand on a shared engine it is dangerous.
55
+ The objdir's chrome and widget files are symlinks into `engine/`, so the
56
+ browser reads the sources when it loads them, after the build step. A
57
+ mutation is therefore live for every build and suite that touches the file
58
+ while it is in place: a peer's `test --build` can build and test the
59
+ neuter, a `--build` run re-deploys `components/` over an engine-side
60
+ control, and a neuter restored at `Build complete` (or after the first
61
+ `TEST_START`) is already gone for the later shards of the same run.
62
+
63
+ `fireforge test --negative-control <diff> <paths…>` makes the control part
64
+ of the run:
65
+
66
+ 1. The diff is read (resolved against the project root), parsed, and every
67
+ hunk is checked against the current file before anything else, so a
68
+ stale control is refused (`note=negative-control`) before the build. A
69
+ `components/` target is checked after the build instead, because the
70
+ build redeploys it.
71
+ 2. With `--build`, the pre-test build (including the Furnace deploy)
72
+ runs first and packages the clean sources.
73
+ 3. After every gate has passed and right before the harness spawns, the
74
+ control is applied. It stays in place until the run, every shard of it,
75
+ has finished.
76
+ 4. The files are restored byte-exact. Then the next repetition (if any)
77
+ re-applies it, and only after the last restore is the lock released.
78
+
79
+ The diff is `git diff` output (a `diff --git` header per file). Paths may be
80
+ project-relative (`engine/…`, `components/…`) or engine-relative, so both
81
+ `git diff` at the project root and `git -C engine diff` work. A
82
+ `components/custom/<name>/<file>.mjs|.css` or
83
+ `components/overrides/<name>/<file>` path is applied to the engine copy
84
+ Furnace deployed it to, since that is what the run reads; the component
85
+ source itself is never touched. Only modifications of existing text files
86
+ are accepted: a created, deleted, renamed or binary file, or a symlinked
87
+ target, is refused. Hunks must match exactly; the only tolerance is a line
88
+ offset, as in `git apply`, never fuzz.
89
+
90
+ The verdict says how the control came out with `control=`:
91
+
92
+ - `caught`: every repetition went red with `reason=test-failures`, which is
93
+ what a control is for. The invocation exits 0.
94
+ - `missed`: a repetition stayed green with the mutation in place, so the
95
+ selected tests do not detect it. Exit 5, like a red suite.
96
+ - `inconclusive`: a run crashed, found no tests, was refused, or did not
97
+ run. The exit code is that failure's.
98
+
99
+ The status word keeps describing what the harness saw, so a caught control
100
+ reads `FIREFORGE-VERDICT: FAIL reason=test-failures … control=caught` and
101
+ exits 0. Key a control's success on `control=caught`, not on `PASS`.
102
+
103
+ A one-run control keeps its own counts on the verdict line; a repeated or
104
+ mixed one aggregates them as above. The mutation targets what the objdir
105
+ symlinks. A file the build preprocesses or copies (rather than links) is
106
+ read from the objdir, not from `engine/`, so a control on it stays green
107
+ and reports `missed`: that result is the signal to target the file the
108
+ browser actually loads.
109
+
110
+ ### The restore safety model
111
+
112
+ - **Only under the lock.** Apply, run and restore all happen inside the
113
+ engine-session lock hold of the `fireforge test` invocation, and the
114
+ control is applied after the pre-test build. No peer build or test
115
+ observes it, and the build cannot overwrite it or trip `stale-build`
116
+ over it.
117
+ - **Journal first.** Before the first mutated byte is written, the
118
+ original bytes of every target go to `.fireforge/negative-control/`
119
+ with a journal of each file's original and mutated SHA-256, mode and
120
+ timestamps. Every write (originals, journal, mutation, restore) is an
121
+ atomic temp-file-and-rename. A failure while writing the mutation puts
122
+ back whatever was written before the run can start.
123
+ - **Byte-exact restore, verified.** Restore writes the original bytes back,
124
+ re-applies the original mode and timestamps (so make-based tooling sees
125
+ no change either), and reads the file back to prove the bytes match. The
126
+ stale-build gate hashes content, so a repeated control's next run is not
127
+ refused.
128
+ - **Never clobber.** A file is only restored while it still holds exactly
129
+ the bytes the control wrote. A file someone edited while the control ran
130
+ (an agent working outside the lock, say) keeps that edit. Its original
131
+ stays in the journal directory, and the run ends
132
+ `FAIL reason=inconclusive note=negative-control-restore` with the paths
133
+ named. Reconcile by hand, then delete the directory.
134
+ - **Every exit path.** Pass, red, crash and a refusal after the apply all
135
+ restore in the run's own control flow. SIGINT and SIGTERM restore in the
136
+ signal pipeline after the harness has shut down and before the held
137
+ locks are released. A restore already in flight is awaited, not
138
+ repeated.
139
+ - **Even after SIGKILL.** A SIGKILLed FireForge cannot restore, but the
140
+ journal survives it. Every later `fireforge test` checks for it first,
141
+ under the lock, before any gate or build: a file still holding the
142
+ control's bytes is restored, with a notice; any other state refuses with
143
+ `note=negative-control-pending` rather than guess.
144
+
145
+ The shorthand `--mutate <file> <old> <new>` that was proposed alongside this
146
+ was not added. Commander options take one value, so a three-value option
147
+ would have to swallow the positional test paths. A one-hunk diff expresses
148
+ the same edit and can be reviewed before it runs.
149
+
22
150
  ## Pathless runs must choose a mode
23
151
 
24
152
  - `--auto` forwards mach's own auto-selection.
@@ -32,22 +160,81 @@ invocation.
32
160
  When packageable engine files changed since the last successful FireForge
33
161
  build, `test` fails before launching stale artifacts. Use `--build` to
34
162
  refresh, or `--allow-stale-build` only for intentional out-of-band rebuilds.
163
+ The refusal prints the one-line repackaging command for the requested paths
164
+ (`fireforge test --build-only <paths>`), so a build-less sitting can
165
+ repackage without dispatching anything.
166
+
167
+ Two kinds of edit do not count as stale, because the packaged runtime
168
+ already serves them. FireForge reads the objdir's install manifests
169
+ (`obj-*/_build_manifests/install/*`, `obj-*/faster/install_*`) to decide:
170
+
171
+ - a file whose every objdir copy is a symlink resolving to the edited source
172
+ (the usual dev-build shape on macOS and Linux for chrome scripts and test
173
+ files);
174
+ - a test file the build does not package: one installed only by `mach test`
175
+ itself at dispatch (`_test_files`), or a test-tree file no manifest installs.
176
+
177
+ A copy, a preprocessed file, a symlink that does not resolve to the source,
178
+ or an unreadable objdir keeps the edit stale. Exempted edits are named on
179
+ the run (and in the refusal, when other edits are stale). On a
180
+ `--perf-samples` run the refusal also says which stale files are staging
181
+ inputs of the sampled test and which lie elsewhere, so a sample whose fixture
182
+ is unaffected can proceed knowingly under `--allow-stale-build`.
183
+
184
+ ### Packaging coverage
35
185
 
36
186
  `--build-only` packages mixed-harness paths once, and then each harness half
37
- can run without `--build`. Scoped builds retain prior coverage automatically
38
- when engine HEAD, `engine/mozconfig` and recorded staging inputs outside the
39
- rebuilt scope are unchanged. Changed files explicitly included in this
40
- build are allowed. The fingerprints include test scripts and support files,
41
- not only packageable chrome sources. A failed anchor narrows the claim and
42
- prints the dropped paths; `--extend-coverage` instead refuses. Baselines
43
- without complete staging-input fingerprints require a new build before they
44
- can retain prior coverage.
45
- New dirty inputs outside the rebuilt scope invalidate retention conservatively,
46
- including shared fixtures outside test directories.
187
+ can run without `--build`. A plain mixed `test --build` now does both in one
188
+ invocation (see [Several runs under one lock hold](#several-runs-under-one-lock-hold)). Coverage is kept per staged path. Each path of a
189
+ scoped build records its own staging inputs: its manifest directory (the
190
+ nearest directory at or above it with a test manifest) plus whatever a
191
+ manifest there names outside it (`support-files`, `head`, `!/` shared
192
+ fixtures), with their content at that build. A later scoped build keeps every
193
+ earlier path whose own inputs and `engine/mozconfig` are unchanged, whatever
194
+ moved elsewhere in the engine, including a peer's edits and an engine HEAD
195
+ move that touches none of them. A path whose inputs changed is dropped and
196
+ named, with the inputs that changed. Changed inputs this build restages are
197
+ allowed.
198
+
199
+ A scoped build after a full build keeps the claim full while nothing outside
200
+ its scope moved. Otherwise it carries the full build's anchor forward, and a
201
+ later build-less run over any path stays covered while that path's own
202
+ staging inputs are unchanged since the full build. A refusal names the inputs
203
+ that did change.
204
+
205
+ `--extend-coverage` remains accepted. Retention is now the default, so the
206
+ flag only turns a drop into a refusal before the build. Before 0.50.0 a
207
+ scoped build without the flag replaced the claim whenever anything outside
208
+ its scope had changed, and the flag refused unless engine HEAD, the
209
+ mozconfig and every staging input recorded by the previous build were
210
+ unchanged. Baselines without complete staging-input fingerprints (older than
211
+ 0.49.0) cannot prove anything unchanged and are dropped once.
212
+
213
+ ### Furnace components in a pre-test build
214
+
215
+ `test --build` and `--build-only` run `furnace apply` first, like
216
+ `fireforge build`, and that apply deploys every component whose
217
+ `components/` source changed since its last deploy, another session's
218
+ undeployed edits included. The run names those components in a notice and
219
+ on the verdict line (`furnace-applied=<names>`). `--no-furnace-apply`
220
+ packages `engine/` as it stands, for example for a before-capture, names the
221
+ components with undeployed edits it left out, and stamps
222
+ `furnace-apply=skipped`.
47
223
 
48
224
  When `patchLint.checkJs` and `patchLint.checkJsTestFiles` are enabled, named
49
225
  pre-test builds check the selected test scripts before deployment or mach.
50
- This includes new scripts not yet owned by an exported patch.
226
+ This includes new scripts not yet owned by an exported patch. The check runs
227
+ before the engine lock is requested, so a type error refuses at once instead
228
+ of after a place in the lock queue. An unowned `head*.js` in a named test's
229
+ directory joins the check when that test loads it with `loadSubScript` or a
230
+ manifest beside it lists it in `support-files`, so a suite moved onto a new
231
+ support head can be proven before the head is exported. A notice names each
232
+ such head and the `re-export --scan --scan-file` command that adopts it.
233
+
234
+ `--restart-between-tests` forwards mochitest's flag of the same name, which
235
+ restarts the browser between test files (the run shape a manifest's
236
+ `standalone` key asks for). It is refused for an xpcshell or generic
237
+ dispatch, like `--shuffle`.
51
238
 
52
239
  ### Shared browsers, ports and host state
53
240
 
@@ -61,8 +248,39 @@ launchd-parented app remains unattributed and is not automatically killed.
61
248
  `test --wait-port [seconds]` waits for mochitest and Marionette listeners.
62
249
  `build --wait-browser [seconds]` queues a full or UI build behind the browser.
63
250
  Bare flags wait 60 seconds; explicit values accept 1–3600 seconds. They are
64
- independent of the FireForge lock budget. On expiry the normal preflight
65
- refusal and its remedy are preserved.
251
+ independent of the FireForge lock budget, and they are spent BEFORE the
252
+ engine session lock is requested, so a run waiting out a browser or a port
253
+ does not hold every peer's build, test and re-export behind it. The run then
254
+ joins the lock queue and re-checks once under the lock; the normal preflight
255
+ refusal and its remedy apply if the resource is busy again (or still busy)
256
+ there. With `--kill-stale-marionette` a reapable stale browser counts as
257
+ clear before the lock, and the port waits stay under the lock, where the
258
+ stale listener can be reaped.
259
+
260
+ A path-shaped argument right after `--wait-lock`, `--wait-browser`,
261
+ `--wait-port` or `--shuffle` (it has a `/`, a file extension, or is `.`) is
262
+ a test path, not the flag's value, and the flag takes its default:
263
+ `fireforge test --wait-lock browser_x.js` waits 60 seconds and runs
264
+ `browser_x.js`. A bare word (`--wait-lock soon`) is still a usage error.
265
+
266
+ `--wait-port` makes sibling checkouts take turns. `--auto-ports` (or
267
+ `test.mochitestPorts: "auto"` in `fireforge.json`) lets them run browser
268
+ suites at the same time. Desktop `mach mochitest` has no `--http-port` or
269
+ `--ssl-port` option; those exist only for Android and iOS. Recent engines
270
+ do something better instead: `testing/mochitest/runtests.py` bind-probes
271
+ the HTTP server, ssltunnel and WebSocket ports before starting its servers,
272
+ and the Marionette port before launching the browser, moves each busy one to
273
+ a free port, and routes `mochi.test:8888` to wherever the server landed.
274
+ FireForge's own refusal used to fire before that fallback could. In auto
275
+ mode the mochitest-server and Marionette gates step aside, with a notice
276
+ naming the holder, when the holder is not this checkout's own (a sibling
277
+ checkout's live suite or an unrelated listener). This checkout's own
278
+ leftovers are refused exactly as before, and `--kill-stale-marionette`
279
+ still applies to them. Auto mode takes effect only when the engine's
280
+ `runtests.py` carries the fallback; otherwise a notice says so and the
281
+ default refusal stays. It never applies to `--doctor`'s Marionette probe,
282
+ which launches its own browser on that port. The default (`"fixed"`) is
283
+ unchanged.
66
284
 
67
285
  Every suite samples host load and the highest CPU process before and after
68
286
  dispatch. Load at least 4 or a process at least 90% CPU produces a warning;
@@ -215,9 +433,20 @@ group sweep.
215
433
  Every test run ends with one machine-readable line:
216
434
 
217
435
  ```
218
- FIREFORGE-VERDICT: PASS|FAIL reason=… [note=<class>] [shuffle=<seed>] [orphans-reaped=<n>] [log=<path>]
436
+ FIREFORGE-VERDICT: PASS|FAIL reason=… [note=<class>] [started=<n>/<m> first-unstarted=<path>] [runs=<p>/<n>] [control=<outcome>] [shuffle=<seed>] [orphans-reaped=<n>] [furnace-applied=<names>|furnace-apply=skipped] [log=<path>]
219
437
  ```
220
438
 
439
+ A run is never `PASS` when its summary reports unexpected results, when the
440
+ harness printed "Application shut down (without crashing) in the middle of a
441
+ test", or when a requested test file never produced a start (or skip)
442
+ marker, even if mach exited 0. A browser that quits cleanly mid-suite does
443
+ exactly that: the counts then cover only the files that ran. Such a run is
444
+ `FAIL reason=test-failures`, and a short start count adds
445
+ `started=<n>/<m> first-unstarted=<path>`: how many requested files started,
446
+ and the first, in dispatch order, that never did. The file count is only
447
+ taken when the output's start lines name the requested files; an xpcshell
448
+ dispatch that proves execution with a result summary alone is not counted.
449
+
221
450
  Automation should branch on this line rather than on the raw process code,
222
451
  and has to treat a missing verdict as a failure. The closed set of `reason=`
223
452
  values and the stdout rules around the line are in
@@ -240,9 +469,9 @@ disables this argument permutation. FireForge also forwards native mach
240
469
  `--shuffle` within each invocation, whose file order remains unseeded and is
241
470
  not replayed by the FireForge seed. `FIREFORGE_SHUFFLE_SEED=<seed>` is available
242
471
  to custom harness `head.js` code for in-file task ordering, and `shuffle=<seed>`
243
- is recorded on the verdict. The option is mochitest-only. Write
244
- `--shuffle=<seed>` or place it after the paths because a bare
245
- `--shuffle <path>` consumes the path as its optional seed.
472
+ is recorded on the verdict. The option is mochitest-only. A path-shaped
473
+ argument right after a bare `--shuffle` is a test path, not a seed (see
474
+ "Shared browsers, ports and host state" above).
246
475
 
247
476
  Exit code 14 (`INCONCLUSIVE`) is not red: it means `engine/` moved while the
248
477
  harness ran and the result was thrown away. Exit 15 (`LOCK_TIMEOUT`) means
@@ -299,14 +528,41 @@ The two phases handle it differently, on purpose:
299
528
 
300
529
  A test run holds the engine-session lock and snapshots the engine git
301
530
  generation before and after the harness run, failing the verdict as invalid
302
- if the tree changed mid-run. To verify beside a busy primary checkout
531
+ if the tree changed mid-run. The checks that need no lock run before it is
532
+ requested: the launchable binary, the named-file checkJs pass, and the
533
+ `--wait-browser`/`--wait-port` budgets. The run log is open from the start,
534
+ so a `reason=lock-timeout` line carries `log=` too.
535
+ A `--perf-samples` run fails only when HEAD
536
+ moved or a changed path is packaged by the running build (an install-manifest
537
+ source or a test file); an edit no manifest installs, such as a compiled
538
+ source, leaves the sample standing with a warning. To verify beside a busy primary checkout
303
539
  instead, use a [verification tree](verification-trees.md).
304
540
 
305
541
  ## Dev-build gotcha
306
542
 
307
543
  In dev builds, files under `obj-*/dist/bin` may be symlinks back into the
308
- source tree (prefs especially), so edit source prefs directly and keep a
309
- backup before bisection experiments.
544
+ source tree (prefs, chrome JS and CSS, deployed widgets), so edit source
545
+ prefs directly and keep a backup before bisection experiments. The same
546
+ symlinks mean a running suite reads `engine/` when its browser loads a file,
547
+ after any `--build` step: `Build complete` is not a snapshot. An edit to
548
+ `engine/`, yours or a peer session's, made before the `FIREFORGE-VERDICT`
549
+ line can reach the running suite, so keep a temporary edit, such as a
550
+ hand-made neuter, in place until the verdict line, or use
551
+ `--negative-control` for deliberate mutations. `fireforge test --help` says
552
+ so too.
553
+
554
+ ## Missing launchable binary
555
+
556
+ `test` checks for the launchable binary before it requests the engine lock,
557
+ and names the cause when the bundle is incomplete. Every protected mach
558
+ build (`build`, `build --ui`, `test --build`) records itself in
559
+ `.fireforge/build-run.json` while it runs. A successful build removes the
560
+ record, a failed one keeps it with its exit code, and a build whose process
561
+ died part-way leaves it marked running with a PID that no longer exists. The
562
+ refusal (`note=no-launchable-binary`) then says, in any session, that the
563
+ last build was interrupted or failed, by which command and when. A missing
564
+ binary while a build is still running is not refused early: the run queues
565
+ behind that build, which relinks the bundle before it releases the lock.
310
566
 
311
567
  ## Repository release gate
312
568
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.49.2",
3
+ "version": "0.50.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",