@hominis/fireforge 0.49.1 → 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 (336) hide show
  1. package/CHANGELOG.md +41 -0
  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-templates.d.ts +8 -3
  35. package/dist/src/commands/furnace/create-templates.js +8 -23
  36. package/dist/src/commands/furnace/create.js +2 -2
  37. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  38. package/dist/src/commands/furnace/deploy-named.js +107 -0
  39. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  40. package/dist/src/commands/furnace/deploy.js +50 -86
  41. package/dist/src/commands/furnace/diff.js +26 -3
  42. package/dist/src/commands/furnace/index.js +66 -41
  43. package/dist/src/commands/furnace/init.js +13 -6
  44. package/dist/src/commands/furnace/override.js +4 -2
  45. package/dist/src/commands/furnace/preview.js +4 -3
  46. package/dist/src/commands/furnace/refresh.js +2 -2
  47. package/dist/src/commands/furnace/remove.js +4 -3
  48. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  49. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  50. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  51. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  52. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  53. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  54. package/dist/src/commands/furnace/rename.d.ts +2 -1
  55. package/dist/src/commands/furnace/rename.js +28 -66
  56. package/dist/src/commands/furnace/scan.d.ts +9 -4
  57. package/dist/src/commands/furnace/scan.js +11 -9
  58. package/dist/src/commands/furnace/sync.js +3 -2
  59. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  60. package/dist/src/commands/furnace/validate-report.js +91 -0
  61. package/dist/src/commands/furnace/validate.d.ts +1 -1
  62. package/dist/src/commands/furnace/validate.js +127 -114
  63. package/dist/src/commands/furnace/validation-output.js +14 -3
  64. package/dist/src/commands/lint-per-patch.js +31 -60
  65. package/dist/src/commands/lint-report.d.ts +8 -4
  66. package/dist/src/commands/lint-report.js +15 -5
  67. package/dist/src/commands/lint.js +15 -34
  68. package/dist/src/commands/manifest.js +2 -0
  69. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  70. package/dist/src/commands/patch/delete.d.ts +1 -1
  71. package/dist/src/commands/patch/delete.js +13 -98
  72. package/dist/src/commands/patch/index.d.ts +1 -1
  73. package/dist/src/commands/patch/index.js +4 -2
  74. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  75. package/dist/src/commands/patch/lint-ignore.js +65 -28
  76. package/dist/src/commands/patch/move-files-create.js +26 -17
  77. package/dist/src/commands/patch/move-files-into.js +5 -3
  78. package/dist/src/commands/patch/move-files.js +3 -1
  79. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  80. package/dist/src/commands/patch/removal-dependents.js +133 -0
  81. package/dist/src/commands/patch/rename.js +7 -0
  82. package/dist/src/commands/patch/retire.d.ts +34 -0
  83. package/dist/src/commands/patch/retire.js +128 -0
  84. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  85. package/dist/src/commands/patch/split-plan.js +45 -28
  86. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  87. package/dist/src/commands/patch/split-staged.js +202 -0
  88. package/dist/src/commands/patch/split.d.ts +5 -0
  89. package/dist/src/commands/patch/split.js +51 -15
  90. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  91. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  92. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  93. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  94. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  95. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  96. package/dist/src/commands/patch/staged-dependency.js +14 -41
  97. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  98. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  99. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  100. package/dist/src/commands/re-export-adjacent.js +156 -37
  101. package/dist/src/commands/re-export-drift.d.ts +36 -8
  102. package/dist/src/commands/re-export-drift.js +73 -49
  103. package/dist/src/commands/re-export-files.js +11 -8
  104. package/dist/src/commands/re-export-lint.d.ts +2 -1
  105. package/dist/src/commands/re-export-lint.js +23 -10
  106. package/dist/src/commands/re-export-options.js +45 -4
  107. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  108. package/dist/src/commands/re-export-preflight.js +160 -0
  109. package/dist/src/commands/re-export-register.js +42 -17
  110. package/dist/src/commands/re-export-removed.d.ts +12 -1
  111. package/dist/src/commands/re-export-removed.js +41 -11
  112. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  113. package/dist/src/commands/re-export-scan-owner.js +116 -0
  114. package/dist/src/commands/re-export-scan.d.ts +17 -9
  115. package/dist/src/commands/re-export-scan.js +56 -23
  116. package/dist/src/commands/re-export-single.js +22 -6
  117. package/dist/src/commands/re-export-summary.d.ts +28 -0
  118. package/dist/src/commands/re-export-summary.js +36 -0
  119. package/dist/src/commands/re-export.js +37 -24
  120. package/dist/src/commands/rebase/index.js +17 -17
  121. package/dist/src/commands/run.js +16 -2
  122. package/dist/src/commands/status-check.d.ts +55 -16
  123. package/dist/src/commands/status-check.js +92 -37
  124. package/dist/src/commands/status-json.d.ts +22 -9
  125. package/dist/src/commands/status-json.js +47 -32
  126. package/dist/src/commands/status-output.js +3 -1
  127. package/dist/src/commands/status-scope.d.ts +37 -0
  128. package/dist/src/commands/status-scope.js +54 -0
  129. package/dist/src/commands/status.js +66 -23
  130. package/dist/src/commands/test-browser-preflight.js +19 -3
  131. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  132. package/dist/src/commands/test-build-furnace.js +48 -0
  133. package/dist/src/commands/test-build-phase.js +47 -41
  134. package/dist/src/commands/test-checkjs.js +7 -0
  135. package/dist/src/commands/test-diagnose.js +7 -0
  136. package/dist/src/commands/test-modes.d.ts +16 -1
  137. package/dist/src/commands/test-modes.js +24 -1
  138. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  139. package/dist/src/commands/test-multi-verdict.js +111 -0
  140. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  141. package/dist/src/commands/test-orchestrate.js +222 -0
  142. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  143. package/dist/src/commands/test-perf-scope.js +83 -0
  144. package/dist/src/commands/test-prelock.d.ts +51 -0
  145. package/dist/src/commands/test-prelock.js +177 -0
  146. package/dist/src/commands/test-register.js +54 -10
  147. package/dist/src/commands/test-run.js +4 -1
  148. package/dist/src/commands/test-stale-gate.js +57 -27
  149. package/dist/src/commands/test-verdict.d.ts +10 -1
  150. package/dist/src/commands/test-verdict.js +28 -1
  151. package/dist/src/commands/test.d.ts +6 -1
  152. package/dist/src/commands/test.js +66 -60
  153. package/dist/src/commands/token-lint.d.ts +14 -0
  154. package/dist/src/commands/token-lint.js +111 -0
  155. package/dist/src/commands/token.js +10 -0
  156. package/dist/src/commands/verify-scope.d.ts +28 -0
  157. package/dist/src/commands/verify-scope.js +67 -0
  158. package/dist/src/commands/verify.d.ts +10 -2
  159. package/dist/src/commands/verify.js +59 -35
  160. package/dist/src/core/build-baseline-types.d.ts +46 -0
  161. package/dist/src/core/build-baseline-validate.js +19 -0
  162. package/dist/src/core/build-baseline.d.ts +5 -1
  163. package/dist/src/core/build-baseline.js +3 -1
  164. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  165. package/dist/src/core/build-overwrite-guard.js +84 -4
  166. package/dist/src/core/build-prepare.d.ts +7 -0
  167. package/dist/src/core/build-prepare.js +14 -8
  168. package/dist/src/core/build-run-record.d.ts +74 -0
  169. package/dist/src/core/build-run-record.js +136 -0
  170. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  171. package/dist/src/core/checkjs-support-heads.js +104 -0
  172. package/dist/src/core/config-paths.d.ts +2 -2
  173. package/dist/src/core/config-paths.js +8 -0
  174. package/dist/src/core/config-validate-check.d.ts +8 -0
  175. package/dist/src/core/config-validate-check.js +143 -0
  176. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  177. package/dist/src/core/config-validate-file-size.js +91 -2
  178. package/dist/src/core/config-validate-history.d.ts +11 -0
  179. package/dist/src/core/config-validate-history.js +38 -0
  180. package/dist/src/core/config-validate-patch-policy.js +100 -0
  181. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  182. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  183. package/dist/src/core/config-validate-token-scales.js +50 -0
  184. package/dist/src/core/config-validate.js +14 -5
  185. package/dist/src/core/config.d.ts +5 -0
  186. package/dist/src/core/config.js +16 -0
  187. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  188. package/dist/src/core/coverage-dispatch.js +44 -0
  189. package/dist/src/core/coverage-extend.d.ts +70 -45
  190. package/dist/src/core/coverage-extend.js +199 -144
  191. package/dist/src/core/coverage-records.d.ts +97 -0
  192. package/dist/src/core/coverage-records.js +350 -0
  193. package/dist/src/core/destructive.d.ts +7 -4
  194. package/dist/src/core/destructive.js +10 -10
  195. package/dist/src/core/engine-session-lock.d.ts +10 -1
  196. package/dist/src/core/engine-session-lock.js +28 -8
  197. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  198. package/dist/src/core/export-moved-code-shape.js +135 -44
  199. package/dist/src/core/file-lock-queue.d.ts +67 -0
  200. package/dist/src/core/file-lock-queue.js +221 -0
  201. package/dist/src/core/file-lock.d.ts +4 -25
  202. package/dist/src/core/file-lock.js +105 -138
  203. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  204. package/dist/src/core/furnace-apply-output.js +31 -1
  205. package/dist/src/core/furnace-apply.js +12 -2
  206. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  207. package/dist/src/core/furnace-composed-source.js +167 -0
  208. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  209. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  210. package/dist/src/core/furnace-config-validate.js +8 -0
  211. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  212. package/dist/src/core/furnace-css-fragments.js +26 -0
  213. package/dist/src/core/furnace-operation.d.ts +3 -3
  214. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  215. package/dist/src/core/furnace-registration-remove.js +53 -17
  216. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  217. package/dist/src/core/furnace-registration-rename.js +77 -0
  218. package/dist/src/core/furnace-registration.d.ts +14 -4
  219. package/dist/src/core/furnace-registration.js +44 -10
  220. package/dist/src/core/furnace-staleness.d.ts +7 -0
  221. package/dist/src/core/furnace-staleness.js +34 -24
  222. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  223. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  224. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  225. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  226. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  227. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  228. package/dist/src/core/furnace-validate.js +1 -1
  229. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  230. package/dist/src/core/harness-port-fallback.js +96 -0
  231. package/dist/src/core/history-log-file.d.ts +61 -0
  232. package/dist/src/core/history-log-file.js +157 -0
  233. package/dist/src/core/history-log.d.ts +2 -1
  234. package/dist/src/core/history-log.js +2 -1
  235. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  236. package/dist/src/core/json-preserving-edit.js +199 -0
  237. package/dist/src/core/mach.js +8 -1
  238. package/dist/src/core/negative-control-diff.d.ts +39 -0
  239. package/dist/src/core/negative-control-diff.js +136 -0
  240. package/dist/src/core/negative-control.d.ts +138 -0
  241. package/dist/src/core/negative-control.js +356 -0
  242. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  243. package/dist/src/core/objdir-install-manifests.js +226 -0
  244. package/dist/src/core/patch-export-update.d.ts +2 -0
  245. package/dist/src/core/patch-export-update.js +1 -0
  246. package/dist/src/core/patch-export.js +3 -4
  247. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  248. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  249. package/dist/src/core/patch-lint-checkjs.js +8 -11
  250. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  251. package/dist/src/core/patch-lint-code-lines.js +105 -0
  252. package/dist/src/core/patch-lint-cross.js +12 -4
  253. package/dist/src/core/patch-lint-css.d.ts +8 -2
  254. package/dist/src/core/patch-lint-css.js +10 -4
  255. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  256. package/dist/src/core/patch-lint-file-size.js +22 -11
  257. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  258. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  259. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  260. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  261. package/dist/src/core/patch-lint-ignore.js +299 -0
  262. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  263. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  264. package/dist/src/core/patch-lint-size.d.ts +117 -0
  265. package/dist/src/core/patch-lint-size.js +323 -0
  266. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  267. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  268. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  269. package/dist/src/core/patch-lint-token-scale.js +109 -0
  270. package/dist/src/core/patch-lint.d.ts +11 -83
  271. package/dist/src/core/patch-lint.js +64 -286
  272. package/dist/src/core/patch-lock.js +7 -6
  273. package/dist/src/core/patch-manifest-consistency.js +7 -6
  274. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  275. package/dist/src/core/patch-manifest-io.js +19 -8
  276. package/dist/src/core/patch-manifest-validate.js +34 -2
  277. package/dist/src/core/patch-order-legal.d.ts +25 -0
  278. package/dist/src/core/patch-order-legal.js +83 -0
  279. package/dist/src/core/patch-policy-content.d.ts +40 -0
  280. package/dist/src/core/patch-policy-content.js +216 -0
  281. package/dist/src/core/patch-policy.d.ts +16 -3
  282. package/dist/src/core/patch-policy.js +16 -9
  283. package/dist/src/core/patch-retired.d.ts +35 -0
  284. package/dist/src/core/patch-retired.js +54 -0
  285. package/dist/src/core/patch-subset.d.ts +16 -0
  286. package/dist/src/core/patch-subset.js +66 -0
  287. package/dist/src/core/preflight-wait.d.ts +20 -0
  288. package/dist/src/core/preflight-wait.js +38 -0
  289. package/dist/src/core/rebase-from-version.d.ts +46 -0
  290. package/dist/src/core/rebase-from-version.js +71 -0
  291. package/dist/src/core/run-log.d.ts +20 -0
  292. package/dist/src/core/run-log.js +89 -3
  293. package/dist/src/core/run-profile.d.ts +7 -0
  294. package/dist/src/core/run-profile.js +7 -1
  295. package/dist/src/core/smoke-patterns.d.ts +5 -0
  296. package/dist/src/core/smoke-patterns.js +13 -0
  297. package/dist/src/core/test-harness-crash.d.ts +18 -1
  298. package/dist/src/core/test-harness-crash.js +6 -4
  299. package/dist/src/core/test-harness-signature.d.ts +17 -0
  300. package/dist/src/core/test-harness-signature.js +4 -0
  301. package/dist/src/core/test-run-completeness.d.ts +57 -0
  302. package/dist/src/core/test-run-completeness.js +144 -0
  303. package/dist/src/core/test-stale-check.d.ts +27 -1
  304. package/dist/src/core/test-stale-check.js +39 -49
  305. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  306. package/dist/src/core/test-stale-exempt.js +70 -0
  307. package/dist/src/core/token-lint.d.ts +50 -0
  308. package/dist/src/core/token-lint.js +126 -0
  309. package/dist/src/core/tree-guard.js +3 -0
  310. package/dist/src/core/typecheck-shim.d.ts +1 -1
  311. package/dist/src/core/typecheck-shim.js +90 -2
  312. package/dist/src/index.d.ts +8 -3
  313. package/dist/src/index.js +3 -1
  314. package/dist/src/types/commands/index.d.ts +2 -2
  315. package/dist/src/types/commands/options.d.ts +148 -21
  316. package/dist/src/types/commands/patches.d.ts +75 -1
  317. package/dist/src/types/config.d.ts +192 -0
  318. package/dist/src/types/config.js +9 -0
  319. package/dist/src/types/furnace.d.ts +43 -1
  320. package/dist/src/types/furnace.js +7 -0
  321. package/dist/src/types/index.d.ts +1 -1
  322. package/dist/src/utils/options.d.ts +34 -6
  323. package/dist/src/utils/options.js +86 -5
  324. package/dist/src/utils/path-glob.d.ts +15 -0
  325. package/dist/src/utils/path-glob.js +47 -0
  326. package/dist/src/utils/regex.d.ts +15 -3
  327. package/dist/src/utils/regex.js +37 -16
  328. package/docs/configuration.md +282 -9
  329. package/docs/exit-codes.md +18 -1
  330. package/docs/furnace.md +97 -3
  331. package/docs/lifecycle-invariants.md +52 -19
  332. package/docs/machine-output.md +74 -12
  333. package/docs/patch-workflow.md +297 -22
  334. package/docs/run-logs.md +20 -1
  335. package/docs/testing.md +276 -20
  336. package/package.json +2 -2
package/docs/furnace.md CHANGED
@@ -29,7 +29,43 @@ comment:
29
29
  ```
30
30
 
31
31
  `deploy` expands the fragment into the deployed copy only, so editing the
32
- fragment shows up as component drift until the next deploy.
32
+ fragment shows up as component drift until the next deploy. `furnace diff`
33
+ compares the deployed copy against this composed form (and says so in the
34
+ `+++` header), so a freshly deployed includer shows no difference.
35
+
36
+ ## Deploying and applying
37
+
38
+ `furnace deploy` takes any number of names and deploys them under one lock
39
+ hold (`fireforge furnace deploy moz-a moz-b moz-c`), so a batch of widgets
40
+ waits for the engine lock once rather than once per widget. Each name is its
41
+ own atomic unit: it is applied and its state persisted before the next starts.
42
+ An unknown name refuses the whole invocation before anything is written, and
43
+ a stock name is warned about and skipped. With no name, everything deploys.
44
+
45
+ Deploy registers every `.mjs` and `.css` file in a custom component directory
46
+ in `toolkit/content/jar.mn`, including extra modules beside the main one. Add
47
+ the file to the directory and deploy instead of writing the jar line by hand.
48
+ A line that already registers the file anywhere in the jar (same target, or
49
+ same `(widgets/<tag>/<file>)` source) is kept and never duplicated, so mach
50
+ does not fail with "Item already in manifest".
51
+
52
+ The apply and deploy summaries name why each component was skipped, for
53
+ example `Applied 0, skipped 1 (1 already current)`. "Already current" means
54
+ the source is unchanged since the last recorded apply and the engine copy
55
+ still matches it. The apply state is shared by every session on the checkout,
56
+ so that apply may have been another session's, such as a peer's
57
+ `test --build`.
58
+
59
+ ## Renaming
60
+
61
+ `furnace rename <old> <new>` moves the component and rewrites its
62
+ registrations in place. The `customElements.js` entry keeps its position,
63
+ layout and `// <MARKER>:` comments (the marker is `markerComment` from
64
+ `fireforge.json`, else the upper-cased `binaryName`); an entry is only added
65
+ fresh, with the marker, when none existed. The old entry never survives the
66
+ rename. The locale `jar.mn` line is re-pointed only when the old component had
67
+ one, so a `sharedFtl` component never gains a per-component `.ftl` line.
68
+ `--dry-run` prints every file move and engine edit without writing.
33
69
 
34
70
  ## Typed cross-module imports
35
71
 
@@ -42,6 +78,52 @@ An entry counts as Furnace-managed if it is contained in the components
42
78
  workspace. A hand-written entry is therefore preserved, and a managed entry
43
79
  whose helper was deleted is pruned.
44
80
 
81
+ ## Validation as a gate
82
+
83
+ `furnace validate` prints its findings for people. A gate reads
84
+ `furnace validate --json` instead: one document with the verdict
85
+ (`passed`), the counts, per-component counts and every finding, specified in
86
+ [`machine-output.md`](machine-output.md). Errors fail the run.
87
+ `--max-warnings <n>` also fails it when more than `n` warnings remain, so
88
+ `--max-warnings 0` keeps a component tree warning-clean.
89
+
90
+ A finding that was reviewed and accepted is recorded in `furnace.json`
91
+ rather than in a gate script:
92
+
93
+ ```json
94
+ {
95
+ "validationExceptions": [
96
+ {
97
+ "component": "moz-dock-button",
98
+ "check": "no-keyboard-handler",
99
+ "reason": "Activation goes through the inner moz-button."
100
+ }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ An entry suppresses every finding of `check` (the name `validate` prints in
106
+ brackets) on `component`. Each field is required and the reason must be
107
+ non-empty, because the entry is the review record. Suppressed findings are
108
+ still listed, with their reason, under `excepted` in the JSON document and
109
+ counted in the summary. An entry that matches nothing is stale: the finding
110
+ it records is gone, so `validate` reports a `stale-validation-exception`
111
+ warning asking for the entry to be deleted. A run over one component judges
112
+ only that component's entries. `deploy` honours the same entries when it
113
+ validates, but leaves the staleness report to `validate`.
114
+
115
+ `optInChecks` turns on checks that are off by default:
116
+
117
+ ```json
118
+ { "optInChecks": ["icon-button-label"] }
119
+ ```
120
+
121
+ `icon-button-label` warns about an icon-only `<moz-button>` in a component's
122
+ Lit templates (one with an `iconsrc` or an `icon` type, and no `label` or
123
+ text content) that has neither `data-l10n-id` nor `aria-label`, so a screen
124
+ reader would announce it only as "button". A button built with
125
+ `document.createElement` is not seen.
126
+
45
127
  ## Safety
46
128
 
47
129
  Every furnace mutation runs through `runFurnaceMutation`, which gives it a
@@ -53,6 +135,18 @@ commands take which locks, is in
53
135
  [`lifecycle-invariants.md`](lifecycle-invariants.md).
54
136
 
55
137
  `apply`, `deploy` and `sync` accept `--wait-lock [seconds]`, and that budget
56
- covers both locks they take. The twelve mutators without the flag pick up a
57
- session budget from `FIREFORGE_WAIT_LOCK`. See
138
+ covers both locks they take. The leaves that take only the furnace lock
139
+ (`create`, `remove`, `rename`, `refresh`, `scan`, `override`, `preview` and
140
+ the `chrome-doc` pair) apply the flag to that lock. Without the flag, the
141
+ session budget from `FIREFORGE_WAIT_LOCK` applies. See
58
142
  [`configuration.md`](configuration.md#lock-waits).
143
+
144
+ `build` and `test` rewrite deployed component files from their sources before
145
+ building, and `--refuse-unexported-drift` refuses when that would destroy
146
+ engine content no patch records. A deployed file that is a Furnace deployment
147
+ of its own source is classified `deployed-unexported` and allowed: it equals
148
+ the composed current source, differs from it only inside generated fragment
149
+ expansions, or is exactly what the last recorded apply deployed. Those files
150
+ are listed in an informational note, since their patches still owe a
151
+ re-export. Only content that matches no patch body, no baseline and none of
152
+ these source forms is refused.
@@ -73,6 +73,17 @@ command, read the decision table at the bottom first.
73
73
  prompting, so the flag only showed up in the history record. Enforced by
74
74
  `patch-tier-and-lint-ignore.integration.test.ts`.
75
75
 
76
+ The history log is write-only: FireForge appends to it and no command
77
+ reads it back, so nothing depends on its contents or location. It lives
78
+ at `.fireforge/history.jsonl` (or `fireforge.json#history.path`), outside
79
+ the tracked patch queue. Before 0.50.0 it was
80
+ `patches/.fireforge-history.jsonl`; the first append after upgrading
81
+ moves that file, and appends its records when both exist rather than
82
+ overwriting either. Each record carries `invocationId` (one per FireForge
83
+ process), `pid`, `ppid`, and `sessionId` when `FIREFORGE_SESSION_ID` is
84
+ set, so a session sharing the checkout can select its own lines.
85
+ Enforced by `history-log-file.test.ts`.
86
+
76
87
  7. **A verification tree never mutates shared state.** Trees are full
77
88
  project-root CoW snapshots under `.fireforge/trees/<name>`, with no
78
89
  merge-back model.
@@ -204,16 +215,28 @@ command, read the decision table at the bottom first.
204
215
  `git-readonly-index.test.ts`, against a real repository and a real index
205
216
  mtime.
206
217
 
207
- 10. **Lock queues are observable, and leaving one is as reliable as joining
208
- it.** A waiter that contends registers a file in `<lock>.waiters/` named
209
- `<startedAtMs>-<pid>-<uuid>` and deregisters in a `finally` that covers
210
- both outcomes: acquired the lock, or timed out. Depth counts only entries
211
- whose PID is still alive, so a killed waiter cannot inflate it forever,
212
- and the registry is advisory: failures to register are logged verbosely
213
- and never fail an acquisition. `fireforge status --lock` and the
214
- `--wait-lock` progress line read it. Enforced by `lock-visibility.test.ts`
215
- (a real concurrent waiter) and `engine-session-lock.test.ts` (queue
216
- position in the wait line).
218
+ 10. **Lock queues are observable, first come first served, and leaving one
219
+ is as reliable as joining it.** A waiter that contends registers a ticket
220
+ in `<lock>.waiters/` named `<startedAtMs>-<pid>-<uuid>` and deregisters in
221
+ a `finally` that covers both outcomes: acquired the lock, or timed out.
222
+ A newcomer that finds live tickets takes one before trying the lock at
223
+ all. Since 0.50.0 the tickets decide the grant: only the waiter with no
224
+ live ticket ahead of it (arrival time, then ticket name) attempts the
225
+ lock, so "You are next" is a promise. Before, every waiter raced `mkdir`
226
+ and a waiter whose poll had backed off lost each free moment to later
227
+ arrivals. Because a ticket now blocks the queue, a dead one must not: a
228
+ ticket whose PID is gone (or reused, where the start tick can tell) is
229
+ skipped and removed, and one its owner has not refreshed for 30 s (every
230
+ waiter rewrites its ticket every 5 s) is skipped, which covers a stopped
231
+ waiter and PID reuse elsewhere. A registry that cannot be written falls
232
+ back to the old unordered contention rather than failing an acquisition.
233
+ This applies to every `withFileLock` lock: the engine-session, patch,
234
+ furnace and build locks among them. `fireforge status --lock` and the
235
+ `--wait-lock` progress lines read it, and the lines name the ordering
236
+ rule. Enforced by `file-lock-queue.test.ts` (arrival order against faster
237
+ later pollers, crashed, killed and stopped waiters, timeouts),
238
+ `lock-visibility.test.ts` (a real concurrent waiter) and
239
+ `engine-session-lock.test.ts` (queue position in the wait line).
217
240
 
218
241
  11. **A manifest repair preserves what it cannot recompute, or refuses.**
219
242
  `doctor --repair-patches-manifest` is a merge: only `filesAffected` and
@@ -346,7 +369,10 @@ run and how large the budget is.
346
369
 
347
370
  The budget is not the engine lock's alone. It covers every lock the command
348
371
  takes, which for the three furnace commands means the per-root furnace lock as
349
- well (see the `furnace-operation.ts` section). `FIREFORGE_WAIT_LOCK=<seconds>`
372
+ well (see the `furnace-operation.ts` section). The furnace leaves that take
373
+ only the furnace lock (`create`, `remove`, `rename`, `refresh`, `scan`,
374
+ `override`, `preview`, `chrome-doc create`/`remove`) honour the flag too, for
375
+ that one lock. `FIREFORGE_WAIT_LOCK=<seconds>`
350
376
  sets the same budget for a whole session, parsed with the same bounds as the
351
377
  flag so the two cannot drift, and an explicit budget at a call site always
352
378
  wins. Commands that take no lock accept `--wait-lock` and ignore it, so a
@@ -364,11 +390,10 @@ cleanups and a `completed` flag), signal rollback with bounded timeouts,
364
390
 
365
391
  The lock's wait budget is `options.lockTimeoutMs`, threaded from `--wait-lock`
366
392
  by `waitLockMutationOptions`, which does the seconds-to-milliseconds
367
- conversion once. It falls back to `sessionWaitLockMs()`
368
- (`FIREFORGE_WAIT_LOCK`) and then to `DEFAULT_LOCK_TIMEOUT_MS`. That fallback
369
- is what covers the twelve furnace mutators that declare no flag (`create`,
370
- `remove`, `rename`, `refresh`, `scan`, `override`, `chrome-doc`, and the
371
- rest). A contended refusal here names the furnace lock, says it is the second
393
+ conversion once. Every furnace leaf that takes the lock threads its own
394
+ flag. It falls back to `sessionWaitLockMs()` (`FIREFORGE_WAIT_LOCK`) and then
395
+ to `DEFAULT_LOCK_TIMEOUT_MS`; that fallback is the only budget for callers
396
+ that thread none (the pre-build apply, `doctor --repair-furnace`). A contended refusal here names the furnace lock, says it is the second
372
397
  of two, and points at `--wait-lock` and `FIREFORGE_WAIT_LOCK`. A budget that
373
398
  is paid in full and then refuses anyway is the worst shape a budget can have,
374
399
  so the refusal must not read as the engine lock's.
@@ -404,7 +429,9 @@ hold it.
404
429
 
405
430
  The destructive-operation contract: `confirmDestructive` (summary, prompt,
406
431
  `--yes`, `--dry-run`, conflict refusal, `--force-unsafe`) and the
407
- `.fireforge-history.jsonl` audit log.
432
+ append-only history log (`src/core/history-log-file.ts` resolves its path,
433
+ migrates a legacy `patches/.fireforge-history.jsonl`, and stamps each
434
+ record's invocation and session).
408
435
 
409
436
  ### `src/core/tree-store.ts`
410
437
 
@@ -444,9 +471,15 @@ On SIGINT or SIGTERM, `bin/fireforge.ts`:
444
471
  work: the group sweep and the caller's `postCloseSweep` (the harness
445
472
  helper reap in `fireforge test`). Both run before the child's closure
446
473
  settles, so a forwarded SIGTERM cannot race `process.exit` past them.
447
- 3. Force-releases the furnace lock directories of active operations, since
474
+ 3. Restores any `test --negative-control` mutation still applied
475
+ (`restoreActiveNegativeControlsForSignal`), after child shutdown and
476
+ before any lock is released, so the next lock holder never builds or
477
+ tests the neuter. A restore the run itself already started is awaited.
478
+ A control a SIGKILL left behind is undone by the next `fireforge test`
479
+ from its on-disk journal (`src/core/negative-control.ts`).
480
+ 4. Force-releases the furnace lock directories of active operations, since
448
481
  `withFileLock`'s `finally` will never run past `process.exit`.
449
- 4. Calls `process.exit(130 | 143)`.
482
+ 5. Calls `process.exit(130 | 143)`.
450
483
 
451
484
  A parent that dies without signalling FireForge (SIGKILL on the `npm` above
452
485
  it) enters no handler here. `fireforge test` covers that case itself with
@@ -53,22 +53,28 @@ if (options.json === true) {
53
53
 
54
54
  ## Current surfaces
55
55
 
56
- | Surface | Shape | Notes |
57
- | ---------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
- | `status --json` | `{ schemaVersion: 1, … }` | Error envelope on three engine preconditions |
59
- | `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope added in 0.44.0 |
60
- | `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
61
- | `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
62
- | `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [shuffle=<seed>] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
63
- | `lint --per-patch --report <path>` | `{ schemaVersion: 1, … }` | Written to a file, never read back by FireForge |
64
- | `status --raw` | `git status --porcelain` lines | Not JSON, but still a machine surface: it engages the same stdout discipline, so rules 3 and 4 apply and rules 1 and 2 do not. A clean tree prints nothing |
65
- | `build` log announcement | `Full build output: <path>` | Not JSON. `build` prints no verdict line, so it names its run log directly on the way out |
56
+ | Surface | Shape | Notes |
57
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `status --json` | `{ schemaVersion: 1, … }` | Error envelope on three engine preconditions |
59
+ | `status --json` under `--check` | adds `check: { enabled, failOn, allow?, failOnUnknown?, failed, offenders }` | Since 0.50.0 on the full payload too, not only `--summary`. `allow` and `failOnUnknown` appear only when set. An offender failing only through `--fail-on-unknown` carries `unnamed: true`. `--patches` adds `scope: { patches }` |
60
+ | `furnace validate --json` | `{ schemaVersion: 1, passed, summary, maxWarnings?, components, issues, excepted, staleExceptions }` | `issues` counts after `validationExceptions`, stale-exception warnings included. `excepted` carries each suppressed finding with its `reason` |
61
+ | `check --json` | `{ schemaVersion: 1, verdict, reason?, summary, steps }` | One entry per step: `name`, `kind`, `command`, `status` (`pass`/`fail`/`skipped`), `exitCode`, `durationMs`, `detail`, and on failure `outputTail` (`timedOut` when killed at its budget). No verdict line under `--json` |
62
+ | `check` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL [reason=step-failures\|lock-timeout failed=<steps>] steps=<n> skipped=<n>` | Not JSON. The last stdout line, after the step table, sealed like `test`'s. Its `reason=` set is its own, not `test`'s |
63
+ | `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope added in 0.44.0 |
64
+ | `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
65
+ | `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
66
+ | `token lint --json` | `{ schemaVersion: 1, tokensCssPath, tokenPrefix, filesScanned, findings }` | Each finding has `check`, `file`, `line`, `token`, `message`. The document is written even when findings exist; the exit is then non-zero, as with `status --check --json` |
67
+ | `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [started=<n>/<m> first-unstarted=<path>] [runs=<p>/<n>] [control=<outcome>] [shuffle=<seed>] [furnace-applied=<names>\|furnace-apply=skipped] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
68
+ | `lint --per-patch --report <path>` | `{ schemaVersion: 1, patches, queueIssues, totals, … }` | Written to a file. `queueIssues` holds the cross-patch findings; `patch staged-dependency --add --from` reads back their `stagedDeclarations` |
69
+ | `status --raw` | `git status --porcelain` lines | Not JSON, but still a machine surface: it engages the same stdout discipline, so rules 3 and 4 apply and rules 1 and 2 do not. A clean tree prints nothing |
70
+ | `build` log announcement | `Full build output: <path>` | Not JSON. `build` prints no verdict line, so it names its run log directly on the way out |
66
71
 
67
72
  `--raw` is why `cli.ts` reads `process.argv` for `--json` or `--raw` before
68
73
  dispatching. Rule 3 is about who owns stdout, and that question is settled
69
74
  the same way for a porcelain stream as for a JSON document.
70
75
 
71
- `doctor` and `verify` have no `--json` mode. `doctor` already computes a
76
+ `doctor` and `verify` have no `--json` mode (`fireforge check` reports
77
+ `verify` as a step with its exit code and output tail). `doctor` already computes a
72
78
  serializable `DoctorCheck[]`, so adding one is cheap if a consumer asks for
73
79
  it. Follow the rules above rather than inventing a shape.
74
80
 
@@ -111,7 +117,47 @@ tokenises `key=value` pairs is unaffected, and a gate that names no class
111
117
  emits no `note=` at all. The classes are not a closed set and may grow, so
112
118
  consumers should treat an unrecognised one as opaque. Current values:
113
119
  `browser-busy`, `stale-browser`, `stale-browser-kill-failed`, `marionette-port-busy`, `mochitest-port-busy`,
114
- `coverage-replaced`, `stale-build`, `stale-components`.
120
+ `coverage-replaced`, `stale-build`, `stale-components`, `no-launchable-binary`, `negative-control` (the
121
+ control diff was refused), `negative-control-pending` (an interrupted run's
122
+ control could not be undone safely). `note=negative-control-restore` rides a
123
+ `reason=inconclusive` line whose control could not be restored byte-exact.
124
+
125
+ ### Multi-run invocations: `FIREFORGE-RUN:`, `runs=` and `control=`
126
+
127
+ A mixed-harness request, `--repeat <n>` and `--negative-control <diff>` run
128
+ several mach invocations under one lock hold (see
129
+ [`testing.md`](testing.md#several-runs-under-one-lock-hold)). Each sub-run
130
+ writes one raw stdout line before the verdict:
131
+
132
+ ```
133
+ FIREFORGE-RUN: PASS|FAIL [reason=…] [note=…] [checks=<n>] [unexpected=<n>] [shards=<p>/<t>] run=<i>/<n> [harness=xpcshell|mochitest]
134
+ ```
135
+
136
+ Its keys are the ones that sub-run's verdict line would have carried alone.
137
+ A `FIREFORGE-RUN:` line is never a verdict: the invocation still ends with
138
+ exactly one `FIREFORGE-VERDICT:` line. With more than one planned run it
139
+ carries `runs=<passed>/<planned>` and no count keys; a failed aggregate takes
140
+ the most structural reason among its runs (`inconclusive`, `preflight`,
141
+ `crash`, `harness-arguments`, `no-tests`, `test-failures`) and that run's
142
+ `note=`. A run that never started (the invocation stopped at a refusal)
143
+ counts toward `<planned>` but not `<passed>`.
144
+
145
+ `control=caught|missed|inconclusive` is present only for a negative control.
146
+ `caught` means every repetition went red with `reason=test-failures`, and
147
+ the process exits 0 even though the status is `FAIL`; `missed` means a
148
+ repetition stayed green (exit 5); `inconclusive` covers every other outcome.
149
+ A one-run control keeps its run's full line, counts included, and appends
150
+ the key.
151
+
152
+ ### The additive `started=` and `first-unstarted=` keys
153
+
154
+ Written together on a `FAIL reason=test-failures` line when a run that mach
155
+ exited 0 on was refused its pass because requested test files never started:
156
+ `started=<n>/<m>` counts the requested test files with a start (or skip)
157
+ marker, and `first-unstarted=<path>` names the first, in dispatch order,
158
+ that has none. A run refused only for a non-zero `unexpected=` count or a
159
+ mid-test shutdown carries neither. Absent on every other line. See
160
+ [`testing.md`](testing.md#the-verdict-line).
115
161
 
116
162
  ### The additive `shuffle=` key
117
163
 
@@ -158,3 +204,19 @@ gains `fireforgeHost: { start, end, powerSourceChanged }`, where each host
158
204
  state includes `load`, `power` and an optional `topProcess` with CPU and
159
205
  command. Consumers should tolerate this additive object. Unknown power
160
206
  state is visible but never treated as a proven transition.
207
+
208
+ On a perf run (`--perf-samples`), an `engine/` change during the run makes
209
+ the verdict `reason=inconclusive` only when HEAD moved or a changed path is
210
+ packaged by the running build: a source an objdir install manifest names, a
211
+ directory one installs from, or a test file. Edits that no manifest installs
212
+ (compiled sources, build files, docs) leave the sample standing with a
213
+ warning naming them. Other runs keep the strict rule: any change is
214
+ inconclusive.
215
+
216
+ ### Additive Furnace keys
217
+
218
+ A `fireforge test --build` or `--build-only` run whose pre-test Furnace apply
219
+ deployed components appends `furnace-applied=<name>,<name>…`: the components
220
+ whose `components/` source changed since their last deploy, which the
221
+ runtime under test therefore includes. `--no-furnace-apply` skips that apply
222
+ and appends `furnace-apply=skipped`. Both are absent otherwise.