@hominis/fireforge 0.52.0 → 0.54.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 (248) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/bootstrap.js +2 -3
  4. package/dist/src/commands/build-prelock.d.ts +3 -3
  5. package/dist/src/commands/build-prelock.js +3 -3
  6. package/dist/src/commands/build.js +4 -0
  7. package/dist/src/commands/declarations-report.d.ts +9 -3
  8. package/dist/src/commands/declarations-report.js +18 -11
  9. package/dist/src/commands/discard.js +3 -3
  10. package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
  11. package/dist/src/commands/doctor-furnace.js +5 -5
  12. package/dist/src/commands/export-adopt-check.d.ts +5 -6
  13. package/dist/src/commands/export-adopt-check.js +5 -6
  14. package/dist/src/commands/export-flow.js +6 -9
  15. package/dist/src/commands/export-staged-registration.d.ts +4 -4
  16. package/dist/src/commands/export-staged-registration.js +4 -4
  17. package/dist/src/commands/forward-import-remedy.d.ts +2 -3
  18. package/dist/src/commands/forward-import-remedy.js +2 -3
  19. package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
  20. package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
  21. package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
  22. package/dist/src/commands/furnace/index.js +2 -2
  23. package/dist/src/commands/lint.js +4 -4
  24. package/dist/src/commands/patch/compact.js +1 -1
  25. package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
  26. package/dist/src/commands/patch/lint-ignore-from.js +3 -4
  27. package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
  28. package/dist/src/commands/patch/move-files-adopt.js +65 -0
  29. package/dist/src/commands/patch/move-files-create.js +41 -20
  30. package/dist/src/commands/patch/move-files-into.d.ts +18 -0
  31. package/dist/src/commands/patch/move-files-into.js +125 -60
  32. package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
  33. package/dist/src/commands/patch/move-files-retire.js +91 -0
  34. package/dist/src/commands/patch/move-files.d.ts +3 -0
  35. package/dist/src/commands/patch/move-files.js +42 -12
  36. package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
  37. package/dist/src/commands/patch/move-size-tier.js +66 -0
  38. package/dist/src/commands/patch/rename.js +1 -1
  39. package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
  40. package/dist/src/commands/patch/reorder-projection.js +49 -0
  41. package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
  42. package/dist/src/commands/patch/reorder-shift.js +307 -0
  43. package/dist/src/commands/patch/reorder.d.ts +9 -15
  44. package/dist/src/commands/patch/reorder.js +52 -50
  45. package/dist/src/commands/patch/split-plan.d.ts +31 -3
  46. package/dist/src/commands/patch/split-plan.js +36 -12
  47. package/dist/src/commands/patch/split-staged.d.ts +2 -2
  48. package/dist/src/commands/patch/split-staged.js +2 -2
  49. package/dist/src/commands/patch/split.js +18 -5
  50. package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
  51. package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
  52. package/dist/src/commands/patch/staged-dependency-from.js +15 -9
  53. package/dist/src/commands/patch/staged-dependency-remove.d.ts +26 -4
  54. package/dist/src/commands/patch/staged-dependency-remove.js +60 -23
  55. package/dist/src/commands/patch/staged-dependency.js +35 -24
  56. package/dist/src/commands/patch/tier.js +2 -2
  57. package/dist/src/commands/patch-rename-map.d.ts +14 -3
  58. package/dist/src/commands/patch-rename-map.js +21 -3
  59. package/dist/src/commands/re-export-adjacent.d.ts +4 -5
  60. package/dist/src/commands/re-export-lint.d.ts +4 -3
  61. package/dist/src/commands/re-export-lint.js +4 -3
  62. package/dist/src/commands/re-export-options.js +8 -2
  63. package/dist/src/commands/re-export-preflight.js +1 -2
  64. package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
  65. package/dist/src/commands/re-export-purity-scope.js +13 -7
  66. package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
  67. package/dist/src/commands/re-export-retired-declarations.js +113 -0
  68. package/dist/src/commands/re-export-single.js +2 -2
  69. package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
  70. package/dist/src/commands/re-export-stale-registrations.js +65 -21
  71. package/dist/src/commands/re-export.js +4 -0
  72. package/dist/src/commands/rebase/continue.js +3 -3
  73. package/dist/src/commands/register.js +5 -4
  74. package/dist/src/commands/run.js +11 -4
  75. package/dist/src/commands/status.js +1 -1
  76. package/dist/src/commands/test-build-furnace.d.ts +1 -2
  77. package/dist/src/commands/test-build-furnace.js +1 -2
  78. package/dist/src/commands/test-build-phase.d.ts +8 -0
  79. package/dist/src/commands/test-build-phase.js +42 -7
  80. package/dist/src/commands/test-control-restore.d.ts +40 -0
  81. package/dist/src/commands/test-control-restore.js +86 -0
  82. package/dist/src/commands/test-dispatch.js +68 -7
  83. package/dist/src/commands/test-multi-verdict.js +1 -0
  84. package/dist/src/commands/test-orchestrate.d.ts +6 -0
  85. package/dist/src/commands/test-orchestrate.js +32 -1
  86. package/dist/src/commands/test-perf-scope.d.ts +1 -1
  87. package/dist/src/commands/test-perf-scope.js +1 -1
  88. package/dist/src/commands/test-prelock.d.ts +18 -8
  89. package/dist/src/commands/test-prelock.js +42 -14
  90. package/dist/src/commands/test-register.js +4 -3
  91. package/dist/src/commands/test-results.d.ts +10 -1
  92. package/dist/src/commands/test-results.js +20 -3
  93. package/dist/src/commands/test-run.d.ts +6 -0
  94. package/dist/src/commands/test-run.js +23 -4
  95. package/dist/src/commands/test-shards.d.ts +7 -3
  96. package/dist/src/commands/test-shards.js +24 -7
  97. package/dist/src/commands/test-stale-gate.d.ts +6 -1
  98. package/dist/src/commands/test-stale-gate.js +40 -2
  99. package/dist/src/commands/test-verdict.d.ts +5 -3
  100. package/dist/src/commands/test.js +14 -2
  101. package/dist/src/commands/token.js +2 -2
  102. package/dist/src/commands/verify.js +1 -1
  103. package/dist/src/commands/watch.js +1 -1
  104. package/dist/src/commands/wire.js +1 -1
  105. package/dist/src/core/build-audit-resolve.d.ts +4 -5
  106. package/dist/src/core/build-audit-resolve.js +4 -5
  107. package/dist/src/core/build-audit-skip.d.ts +2 -2
  108. package/dist/src/core/build-audit.d.ts +3 -4
  109. package/dist/src/core/build-baseline-types.d.ts +24 -3
  110. package/dist/src/core/build-baseline-validate.d.ts +30 -1
  111. package/dist/src/core/build-baseline-validate.js +127 -35
  112. package/dist/src/core/build-baseline.d.ts +8 -2
  113. package/dist/src/core/build-baseline.js +25 -16
  114. package/dist/src/core/build-jar-escalation.d.ts +4 -4
  115. package/dist/src/core/build-jar-escalation.js +4 -4
  116. package/dist/src/core/build-prepare.js +4 -4
  117. package/dist/src/core/config-validate.js +4 -4
  118. package/dist/src/core/coverage-extend.d.ts +5 -3
  119. package/dist/src/core/coverage-extend.js +8 -3
  120. package/dist/src/core/coverage-records.d.ts +4 -5
  121. package/dist/src/core/coverage-records.js +4 -5
  122. package/dist/src/core/destructive.d.ts +2 -2
  123. package/dist/src/core/destructive.js +2 -2
  124. package/dist/src/core/discard-baseline.d.ts +2 -2
  125. package/dist/src/core/discard-baseline.js +3 -3
  126. package/dist/src/core/engine-session-lock.d.ts +1 -1
  127. package/dist/src/core/engine-session-lock.js +1 -1
  128. package/dist/src/core/file-lock-owner.d.ts +5 -5
  129. package/dist/src/core/file-lock-owner.js +5 -5
  130. package/dist/src/core/file-lock-queue.js +6 -8
  131. package/dist/src/core/file-lock-shared.d.ts +4 -4
  132. package/dist/src/core/file-lock-shared.js +4 -4
  133. package/dist/src/core/file-lock.js +7 -10
  134. package/dist/src/core/firefox.js +2 -2
  135. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
  136. package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
  137. package/dist/src/core/furnace-apply.js +3 -3
  138. package/dist/src/core/furnace-operation.js +5 -5
  139. package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
  140. package/dist/src/core/furnace-registration-rename.d.ts +5 -5
  141. package/dist/src/core/furnace-registration-rename.js +5 -5
  142. package/dist/src/core/furnace-registration-validate.d.ts +2 -1
  143. package/dist/src/core/furnace-registration-validate.js +2 -1
  144. package/dist/src/core/furnace-registration.js +2 -2
  145. package/dist/src/core/git-base.js +4 -3
  146. package/dist/src/core/git-diff.js +1 -1
  147. package/dist/src/core/git.js +1 -1
  148. package/dist/src/core/harness-orphans.d.ts +6 -6
  149. package/dist/src/core/harness-orphans.js +6 -6
  150. package/dist/src/core/harness-port-reservation.d.ts +60 -0
  151. package/dist/src/core/harness-port-reservation.js +122 -0
  152. package/dist/src/core/history-log-file.d.ts +9 -9
  153. package/dist/src/core/history-log-file.js +15 -15
  154. package/dist/src/core/history-log.d.ts +3 -3
  155. package/dist/src/core/history-log.js +3 -3
  156. package/dist/src/core/launchable-build.d.ts +39 -0
  157. package/dist/src/core/launchable-build.js +85 -0
  158. package/dist/src/core/marionette-port.js +1 -3
  159. package/dist/src/core/moz-manifest-rules.js +3 -2
  160. package/dist/src/core/negative-control-compiled.d.ts +13 -3
  161. package/dist/src/core/negative-control-compiled.js +27 -5
  162. package/dist/src/core/negative-control-diff.js +31 -6
  163. package/dist/src/core/parser-fallback.d.ts +5 -5
  164. package/dist/src/core/parser-fallback.js +5 -5
  165. package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
  166. package/dist/src/core/patch-identifier-suggest.js +11 -4
  167. package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
  168. package/dist/src/core/patch-lint-checkjs.js +1 -1
  169. package/dist/src/core/patch-lint-css.js +2 -2
  170. package/dist/src/core/patch-lint-file-size.d.ts +5 -7
  171. package/dist/src/core/patch-lint-file-size.js +5 -7
  172. package/dist/src/core/patch-lint-ignore.d.ts +3 -3
  173. package/dist/src/core/patch-lint-ignore.js +3 -3
  174. package/dist/src/core/patch-lint-prettier.d.ts +4 -6
  175. package/dist/src/core/patch-lint-prettier.js +4 -6
  176. package/dist/src/core/patch-lint-projection.d.ts +1 -1
  177. package/dist/src/core/patch-lint-projection.js +1 -1
  178. package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
  179. package/dist/src/core/patch-lint-queue-types.js +2 -2
  180. package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
  181. package/dist/src/core/patch-manifest-consistency.js +2 -2
  182. package/dist/src/core/patch-manifest-io.d.ts +5 -1
  183. package/dist/src/core/patch-manifest-io.js +12 -10
  184. package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
  185. package/dist/src/core/patch-manifest-key-order.js +3 -4
  186. package/dist/src/core/patch-parse.d.ts +3 -3
  187. package/dist/src/core/patch-parse.js +3 -3
  188. package/dist/src/core/patch-policy.d.ts +1 -1
  189. package/dist/src/core/patch-policy.js +1 -1
  190. package/dist/src/core/patch-source-metadata.d.ts +1 -1
  191. package/dist/src/core/patch-source-metadata.js +1 -1
  192. package/dist/src/core/patch-subset.d.ts +5 -3
  193. package/dist/src/core/patch-subset.js +8 -7
  194. package/dist/src/core/patch-transform.d.ts +2 -2
  195. package/dist/src/core/patch-transform.js +2 -2
  196. package/dist/src/core/rebase-session.js +4 -6
  197. package/dist/src/core/register-xpcshell-test.d.ts +28 -7
  198. package/dist/src/core/register-xpcshell-test.js +76 -27
  199. package/dist/src/core/run-log.d.ts +8 -8
  200. package/dist/src/core/run-log.js +8 -8
  201. package/dist/src/core/status-classify.d.ts +1 -2
  202. package/dist/src/core/status-classify.js +1 -2
  203. package/dist/src/core/test-harness-crash.d.ts +5 -3
  204. package/dist/src/core/test-harness-crash.js +13 -14
  205. package/dist/src/core/test-harness-infra.d.ts +32 -0
  206. package/dist/src/core/test-harness-infra.js +69 -0
  207. package/dist/src/core/test-harness-signature.d.ts +6 -0
  208. package/dist/src/core/test-stale-check.d.ts +38 -0
  209. package/dist/src/core/test-stale-check.js +82 -1
  210. package/dist/src/core/test-stall-triage.js +5 -7
  211. package/dist/src/core/token-category.js +2 -2
  212. package/dist/src/core/token-inventory.js +3 -3
  213. package/dist/src/core/token-manager.d.ts +2 -2
  214. package/dist/src/core/token-manager.js +5 -7
  215. package/dist/src/core/token-variant.d.ts +6 -6
  216. package/dist/src/core/token-variant.js +7 -7
  217. package/dist/src/core/typecheck-shim.js +1 -1
  218. package/dist/src/core/wire-destroy.d.ts +1 -1
  219. package/dist/src/core/wire-destroy.js +1 -1
  220. package/dist/src/core/wire-dom-fragment.js +1 -1
  221. package/dist/src/core/wire-init.d.ts +1 -1
  222. package/dist/src/core/wire-init.js +1 -1
  223. package/dist/src/core/wire-subscript.d.ts +1 -1
  224. package/dist/src/core/wire-subscript.js +1 -1
  225. package/dist/src/core/wire-utils.d.ts +2 -2
  226. package/dist/src/core/wire-utils.js +3 -3
  227. package/dist/src/errors/base.d.ts +1 -1
  228. package/dist/src/errors/base.js +1 -1
  229. package/dist/src/errors/rebase.d.ts +1 -1
  230. package/dist/src/errors/rebase.js +1 -1
  231. package/dist/src/types/commands/options.d.ts +38 -3
  232. package/dist/src/types/config.d.ts +5 -7
  233. package/dist/src/utils/options.js +2 -2
  234. package/dist/src/utils/process.js +2 -2
  235. package/dist/src/utils/regex.d.ts +2 -2
  236. package/dist/src/utils/regex.js +3 -3
  237. package/dist/src/utils/sleep.d.ts +5 -6
  238. package/dist/src/utils/sleep.js +5 -6
  239. package/dist/src/utils/validation.js +2 -2
  240. package/docs/configuration.md +28 -23
  241. package/docs/exit-codes.md +8 -11
  242. package/docs/furnace.md +3 -3
  243. package/docs/lifecycle-invariants.md +36 -45
  244. package/docs/machine-output.md +41 -24
  245. package/docs/patch-workflow.md +140 -29
  246. package/docs/run-logs.md +8 -9
  247. package/docs/testing.md +114 -48
  248. package/package.json +1 -1
@@ -2,12 +2,9 @@
2
2
 
3
3
  # Machine-readable output contract (internal)
4
4
 
5
- The `--json` surface is a contract with scripted consumers. Before 0.44.0 it
6
- was four unrelated shapes: `status --json` and `tree list --json` shared a
7
- `schemaVersion: 1` envelope but disagreed on failure, `test` emitted a
8
- `FIREFORGE-VERDICT:` line, and `lint --per-patch --report` wrote a versioned
9
- file that nothing ever reads back. This file states what a `--json` command
10
- has to do, so the next one added does not invent a fifth shape.
5
+ The `--json` surface is a contract with scripted consumers. This file states
6
+ what a `--json` command has to do, so the next one added does not invent a
7
+ shape of its own.
11
8
 
12
9
  ## The rules
13
10
 
@@ -56,11 +53,11 @@ if (options.json === true) {
56
53
  | Surface | Shape | Notes |
57
54
  | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
55
  | `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 }` |
56
+ | `status --json` under `--check` | adds `check: { enabled, failOn, allow?, failOnUnknown?, failed, offenders }` | 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
57
  | `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
58
  | `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
59
  | `check` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL [reason=step-failures\|lock-timeout\|skipped-steps failed=<steps>] steps=<n> skipped=<n> [skipped-steps=<steps>]` | 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 |
60
+ | `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope on failure |
64
61
  | `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
65
62
  | `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
66
63
  | `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` |
@@ -80,7 +77,7 @@ it. Follow the rules above rather than inventing a shape.
80
77
 
81
78
  The run logs behind `log=` are specified in [`run-logs.md`](run-logs.md).
82
79
 
83
- Additive keys since 0.51.0 outside `test`:
80
+ Additive keys outside `test`:
84
81
 
85
82
  - `check --json`: a skipped step carries `skipKind`, `config` when
86
83
  `check.skip` names it and `unavailable` when the project has nothing for
@@ -92,7 +89,7 @@ Additive keys since 0.51.0 outside `test`:
92
89
  `@click`). An exception that matched an error adds a
93
90
  `validation-exception-on-error` error, and a full run under
94
91
  `furnace.json#minComponents` a `component-floor` error on `furnace.json`.
95
- - `furnace validate --json` (since 0.52.0): a `hardcoded-text` finding
92
+ - `furnace validate --json`: a `hardcoded-text` finding
96
93
  carries `files`, the component's module file names it was found in.
97
94
 
98
95
  ## Versioning
@@ -110,6 +107,7 @@ moved.
110
107
  | Reason | Meaning |
111
108
  | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
109
  | `crash` | The harness or the browser died, so there is no trustworthy suite result. |
110
+ | `server-startup` | A mochitest server never came up (`could not start server on port`, `Timed out while waiting for server startup`) before any test ran. An infrastructure failure, retried like `crash` within `--harness-retries`; this reason is what is left once the budget is spent. |
113
111
  | `harness-arguments` | Profile or other harness argument setup failed before any tests could run; a rebuild is not the repair. |
114
112
  | `no-tests` | The run dispatched but nothing ran. |
115
113
  | `test-failures` | The suite ran and reported unexpected results. |
@@ -126,15 +124,19 @@ developer browser holding the objdir. An unattended reader whose pipe kept
126
124
  only the verdict line could not tell those apart, so a preflight refusal that
127
125
  can classify itself also emits `note=<class>`.
128
126
 
129
- `note=` is additive, like `log=`, `signal=` and `shards=`. `reason=` is
130
- unchanged and still comes from the closed set above, so a consumer that
127
+ `note=` is additive, like `log=`, `signal=` and `shards=`. `reason=` always
128
+ comes from the closed set above, so a consumer that
131
129
  tokenises `key=value` pairs is unaffected, and a gate that names no class
132
130
  emits no `note=` at all. The classes are not a closed set and may grow, so
133
131
  consumers should treat an unrecognised one as opaque. Current values:
134
132
  `browser-busy`, `stale-browser`, `stale-browser-kill-failed`, `marionette-port-busy`, `mochitest-port-busy`,
135
133
  `coverage-replaced`, `stale-build`, `stale-components`, `no-launchable-binary`, `negative-control` (the
136
134
  control diff was refused), `negative-control-pending` (an interrupted run's
137
- control could not be undone safely). `note=negative-control-restore` rides a
135
+ control could not be undone safely). Also `stale-compiled`
136
+ (compiled sources changed after the binary under test was linked) and
137
+ `control-built` (the binary still carries a compiled negative control whose
138
+ restore build never ran or failed). An `invalid-build-baseline` refusal
139
+ names the first failing field and its value. `note=negative-control-restore` rides a
138
140
  `reason=inconclusive` line whose control could not be restored byte-exact.
139
141
 
140
142
  ### Multi-run invocations: `FIREFORGE-RUN:`, `runs=` and `control=`
@@ -153,7 +155,7 @@ A `FIREFORGE-RUN:` line is never a verdict: the invocation still ends with
153
155
  exactly one `FIREFORGE-VERDICT:` line. With more than one planned run it
154
156
  carries `runs=<passed>/<planned>` and no count keys; a failed aggregate takes
155
157
  the most structural reason among its runs (`inconclusive`, `preflight`,
156
- `crash`, `harness-arguments`, `no-tests`, `test-failures`) and that run's
158
+ `crash`, `server-startup`, `harness-arguments`, `no-tests`, `test-failures`) and that run's
157
159
  `note=`. A run that never started (the invocation stopped at a refusal)
158
160
  counts toward `<planned>` but not `<passed>`.
159
161
 
@@ -185,7 +187,7 @@ A sharded run prints one `FIREFORGE-SHARD:` line per finished shard, before
185
187
  the verdict line:
186
188
 
187
189
  ```
188
- FIREFORGE-SHARD: PASS|FAIL [reason=…] [checks=<n>] [unexpected=<n>] shard=<i>/<n> path=<label> [failed-files=<path>,…]
190
+ FIREFORGE-SHARD: PASS|FAIL [reason=…] [checks=<n>] [unexpected=<n>] shard=<i>/<n> path=<label> [failed-files=<path>,…] [ports=http:<n>,ssl:<n>,ws:<n>]
189
191
  ```
190
192
 
191
193
  `path=` is the shard's path argument (whitespace replaced by `_`), or a
@@ -195,15 +197,31 @@ aggregate verdict appends `failed-shards=<label>,…` after `shards=` when any
195
197
  shard did not pass; under `--shard-size` `shards=` counts files and
196
198
  `failed-shards=` names files. Both are absent on a green run.
197
199
 
200
+ `ports=` names the mochitest HTTP server, ssltunnel and
201
+ WebSocket ports the shard's harness reported (its "falling back to port"
202
+ lines), in that order and only those it reported; it is absent when the
203
+ harness reported none (an xpcshell shard, or a harness that died before its
204
+ servers). A shard whose server never started says `reason=server-startup`,
205
+ and the aggregate verdict does too when every crashed shard failed that way.
206
+
198
207
  ### Additive keys of parallel and per-file runs
199
208
 
200
209
  `jobs=<n>` marks a `--jobs` run, `lock=shared` a run that held the engine
201
- session lock shared, and `results=<path>` names the `--results` document. A
210
+ session lock shared, and `results=<path>` names the `--results` document.
211
+ A `--jobs` run whose caller set `MOZ_UPLOAD_DIR` appends
212
+ `upload-dirs=<dir>,…`, the per-instance upload directories
213
+ (`<MOZ_UPLOAD_DIR>/job-<i>`, one per instance that ran), and a `--results`
214
+ record whose `test` is `automation.py` (a harness-level result) carries
215
+ `shardFiles`, plus `file` when its shard has exactly one file. A
202
216
  run whose engine changed mid-run only in files the running browser cannot
203
217
  see keeps its verdict and appends `drift-tolerated=<n>`. A negative control
204
- on compiled source appends `control-build=compiled`. Since 0.52.0 a raw log
218
+ on compiled source appends `control-build=compiled`, and
219
+ `control-restore-build=done|failed` for the full build that recompiles the
220
+ restored source after the last run. Every run that reaches
221
+ dispatch appends `build-id=<BuildID>`, read from the launchable build's
222
+ `application.ini` (absent when that file has no `BuildID`). A raw log
205
223
  that starts none of a dispatch's requested files counts as
206
- `started=0/<m> ... started-source=raw` instead of leaving the run uncounted.
224
+ `started=0/<m> ... started-source=raw`.
207
225
 
208
226
  ### The additive `shuffle=` key
209
227
 
@@ -223,9 +241,8 @@ preflight, survivors of an earlier run in this objdir under `--reap-orphans`
223
241
  or `test.reapOrphans: "reap"`; at teardown, helpers this run's harness
224
242
  launched (httpd, websocket server, ssltunnel, process bridge, the browser,
225
243
  `moz-http2`) that were still alive after mach exited. Summed across the census, every
226
- retry attempt and every shard. Absent when nothing was reaped, so a green on
227
- a quiet machine prints the line it always did, and a green after a reap is
228
- distinguishable from it. A run ended by SIGTERM or by the parent-exit
244
+ retry attempt and every shard. Absent when nothing was reaped, so a green after a reap is
245
+ distinguishable from a green on a quiet machine. A run ended by SIGTERM or by the parent-exit
229
246
  watchdog writes its verdict before the teardown reap runs, so that line never
230
247
  carries the key; the reap is reported on stderr instead.
231
248
 
@@ -243,10 +260,10 @@ and measured CPU. These diagnostics annotate a test failure without changing
243
260
  its classifier reason.
244
261
 
245
262
  Perf runs append `power-source=ac|battery|unknown`. On macOS the source is
246
- read from `pmset -g ps`, so charging on AC is correctly `ac`. A known source
263
+ read from `pmset -g ps`, so charging on AC reads `ac`. A known source
247
264
  transition appends `power-changed=true` and refuses the run as
248
- `reason=inconclusive`. The producer's sample JSON retains its fields and
249
- gains `fireforgeHost: { start, end, powerSourceChanged }`, where each host
265
+ `reason=inconclusive`. The producer's sample JSON carries an additional
266
+ `fireforgeHost: { start, end, powerSourceChanged }` object, where each host
250
267
  state includes `load`, `power` and an optional `topProcess` with CPU and
251
268
  command. Consumers should tolerate this additive object. Unknown power
252
269
  state is visible but never treated as a proven transition.
@@ -109,7 +109,7 @@ adjacent to it, meaning the re-export would plausibly have to capture it:
109
109
  moz.build or test-manifest line, `*` globs included).
110
110
 
111
111
  A file in a sibling or nested directory is not adjacent, so a peer
112
- session's unexported test in `test/hominis-tiles/` no longer refuses a
112
+ session's unexported test in `test/hominis-tiles/` does not refuse a
113
113
  patch that owns `test/hominis/browser.toml`. The refusal names every file
114
114
  with its anchor (`beside engine/<dir>` or `registered by engine/<carrier>`).
115
115
 
@@ -139,8 +139,8 @@ fireforge re-export 102 200 --refuse-adjacent-unmanaged --refuse-foreign-drift \
139
139
 
140
140
  `--expect-from <file>` reads `--expect` paths from a file, one per line,
141
141
  with blank lines and `#` comments ignored. Under zsh a list held in one
142
- variable reaches FireForge as a single argument, and splitting it by hand
143
- left every path after the first as a positional patch name.
142
+ variable reaches FireForge as a single argument, and a file avoids splitting
143
+ it by hand.
144
144
 
145
145
  Arguments are checked before the run queues for the engine lock: an empty
146
146
  or unknown patch name, a positional that is an engine file (the refusal
@@ -169,7 +169,7 @@ list rather than the subset left after filtering, so nothing dropped upstream
169
169
  can pass as a silent success.
170
170
 
171
171
  A file can be retired from a selected patch with repeatable
172
- `--expect-removed <path>` on scan-less re-export. This works for an absent
172
+ `--expect-removed <path>`. This works for an absent
173
173
  file the patch created, and for an upstream file the patch modified or
174
174
  deleted whose engine copy is back at HEAD:
175
175
 
@@ -190,7 +190,15 @@ validated before writes, in dry-run too. Other paths keep
190
190
  both refusal checks; retirement grants no unmanaged or foreign-drift
191
191
  exception. Removing every file would empty the patch and requires `patch
192
192
  delete` (or `patch retire`, which keeps the order number) instead.
193
- `--expect-removed` cannot combine with `--scan`, `--files` or `--all`.
193
+ `--expect-removed` cannot combine with `--files` or `--all`. It combines
194
+ with `--scan`, so one call can retire a widget's files from one patch while
195
+ `--scan-file` adopts a new module into another: the retired path leaves the
196
+ owner's file list before its scan, which cannot adopt it back.
197
+
198
+ The same run drops every staged declaration, registration or forward
199
+ import, in any patch of the queue, whose `creates` path it retired and no
200
+ other patch still owns. It prints each one (`would drop` in a dry run) and
201
+ records the drop in the history log (`re-export-staged-prune`).
194
202
 
195
203
  ### Forward imports and new modules
196
204
 
@@ -203,14 +211,35 @@ placeholder. A refused `--scan` adoption, `patch move-files` or `patch split`
203
211
  also writes every declaration it proposes into one lint-report-shaped JSON
204
212
  (`--declarations-out <path>`, by default under `.fireforge/declarations/`)
205
213
  and names the single `patch staged-dependency <holder> --add --from <file>`
206
- call per holding patch that discharges them all. Moving tests out of a patch
207
- that owns a shared manifest no longer needs one declaration typed per
208
- manifest line.
214
+ call per holding patch that discharges them all, so moving tests out of a
215
+ patch that owns a shared manifest needs no declaration typed per manifest
216
+ line.
217
+
218
+ For `patch move-files`, `patch split` and a placed `export`:
219
+
220
+ - The projection of `patch move-files`, `patch split` and a placed `export`
221
+ lints with the queue's whole lint configuration, as the committed `lint
222
+ --per-patch` gate does. With `patchLint.forwardRegistration: "extended"`,
223
+ a shared test manifest's suite entries (`["browser_*.js"]`) are projected
224
+ with the move and written to the `--declarations-out` report, not only its
225
+ `support-files` lines. A dry run that reads clean stays clean after the
226
+ move.
227
+ - A refused `move-files --create` or `split` report names the patch the
228
+ rerun creates as the `owner` (its planned `<order>-<category>-<name>.patch`).
229
+ - The declarations `move-files` and `split` write, and every entry of their
230
+ report, carry a `reason` naming the move, such as
231
+ `"201-ui-tests.patch → 236-ui-suites.patch by patch move-files"`. The
232
+ `--add --from` call the refusal prints then needs no `--reason`; an
233
+ entry's own reason wins over the flag.
209
234
 
210
235
  A re-export also drops a staged registration whose `creates` path no patch
211
236
  in the queue owns any more, for example after a merge deleted the file it
212
237
  registered. The dry run names the drop, and the write records it in the
213
- history log (`re-export-staged-prune`).
238
+ history log (`re-export-staged-prune`). A registration whose
239
+ `creates` file exists in engine/, or is an `--expect-new` path of the run,
240
+ is kept and named ("kept: file exists but no patch owns it yet"): the
241
+ manifest owner can re-export after the declaration and before the scan
242
+ that adopts the file, in either order.
214
243
 
215
244
  An `export --order` (or `--before`/`--after`) refused on a new system
216
245
  module that no patch registers in moz.build says "register first". When the
@@ -236,13 +265,61 @@ The re-export policy gate checks the `filesAffected` the export will write,
236
265
  and only for the patch being written, so one patch's stale row cannot fail
237
266
  the other patches in the same call.
238
267
 
268
+ ### Registering files
269
+
270
+ `fireforge register <path>` adds a new file's line to the build manifest
271
+ that packages or runs it: a jar.mn, a moz.build list, a test manifest. Most
272
+ manifests are kept in sorted order, and the entry is inserted
273
+ alphabetically. `--after <entry>` places it after the line containing that
274
+ substring instead, where the manifest's rule supports it. Elsewhere the
275
+ flag is ignored with a warning, and an `--after` that matches nothing falls
276
+ back to sorted order with a warning.
277
+
278
+ An xpcshell test (`test_*.js` outside `browser/base/content/test/`) goes
279
+ into its directory's `xpcshell.toml` as a `["test_*.js"]` section, placed
280
+ as follows:
281
+
282
+ - The new section is inserted ahead of the `#` comment lines directly above
283
+ the entry it precedes, never between a comment and the entry it describes.
284
+ - `--after <entry>` places the new section after the named entry's whole
285
+ block, with its keys and before the next entry's comment. An exact
286
+ section name matches first, then a substring.
287
+
288
+ ```sh
289
+ fireforge register browser/components/hominis/test/unit/test_HominisStoreQuota.js \
290
+ --after test_HominisStore.js
291
+ ```
292
+
293
+ `--create-manifest` scaffolds a missing manifest and wires it into the
294
+ nearest moz.build instead of refusing with "Manifest not found".
295
+
239
296
  ## Queue maintenance
240
297
 
241
298
  Under `fireforge patch`:
242
299
 
243
300
  - `patch compact` closes ordinal gaps, and is range-aware when
244
301
  `patchPolicy.ranges` is configured.
245
- - `patch reorder` moves a patch.
302
+ - `patch reorder` moves a patch. `patch reorder --shift <n>
303
+ --from <order> [--through <order>]` (no patch name) renumbers a run of
304
+ patches up by `n` in one transaction, to open room for inserts.
305
+ `--through` defaults to the end of the run of consecutive occupied orders
306
+ of `--from`'s category. Retired and reserved slots inside the run move
307
+ with it (a moved reservation is named, so its holder can be told). A shift
308
+ that would land on an order a patch or slot holds past the run is refused.
309
+ Under `patchPolicy.allowGaps: false` the opened orders are recorded as
310
+ reservations (owner `patch reorder --shift`), so the queue stays valid,
311
+ and the `export --order` or `split --order` that takes each one fills it.
312
+ The shift checks the patch policy, projects the cross-patch lint
313
+ (refusing only on errors the shift adds), and commits under the patch
314
+ lock, refusing if the plan changed while it waited for confirmation.
315
+ `--map-out <file>` on `reorder` (every mode) and on `split` writes the
316
+ committed old-to-new filename map as a JSON object, so references to the
317
+ renumbered patches can be rewritten from the file:
318
+
319
+ ```sh
320
+ fireforge patch reorder --shift 4 --from 104 --through 128 --map-out shift-map.json --yes
321
+ ```
322
+
246
323
  - `patch split <source> --files <paths...> --name <name>` carves files out of
247
324
  a patch into a new one as a single transaction, including staged-dependency
248
325
  owner rewrites, and supports `--dry-run`. `patch move-files <from> <to>
@@ -253,6 +330,23 @@ Under `fireforge patch`:
253
330
  declaration whose declaring file moved goes with it, and a declaration the
254
331
  move makes unused is dropped. The patch policy, including the gap rule, is
255
332
  checked before any lint runs.
333
+ - `patch move-files` covers a whole split in one call:
334
+ - `--scan-file <path>` (repeatable) adopts a new engine file that no patch
335
+ owns into the destination (`<to>`, or the patch `--create` makes) in
336
+ the same transaction. Both bodies are linted as the transaction leaves
337
+ them, so the source is never judged on a body grown by an adoption the
338
+ move would shrink again. An owned or missing path is refused.
339
+ - `--retire-empty` lets a move into an existing patch take every file of
340
+ the source: the declarations whose `owner` is the source re-point to the
341
+ destination, the source's own declarations go with its files (one that
342
+ becomes self-owned is dropped), and the source is retired as by `patch
343
+ retire`, its order kept as a retired slot. Without the flag, a move that
344
+ would empty the source is refused and names the flag.
345
+ - A per-patch size refusal on either side names a tier change behind it,
346
+ for example `Tier change: the source becomes test tier (it was general
347
+ tier): 9634 lines against 6000.` A source that keeps only test files is
348
+ held to the test tier's ceiling for every line, so move its test files
349
+ out before its last product file.
256
350
  - `patch rename` also supports `--category` and `--order`.
257
351
  - `patch retire <name>` removes a patch like `patch delete` (same dependency
258
352
  refusal, `--dry-run` and confirmation) but keeps its order number as a
@@ -268,7 +362,7 @@ compact` renumbers through retired slots like any other gap.
268
362
  nothing owns it, so `status` would report it unmanaged. Delete those files
269
363
  first, or pass `--delete-created` to remove them as part of the confirmed
270
364
  operation (under the engine lock as well). Files the patch only modifies
271
- keep their edits and become unmanaged, as before.
365
+ keep their edits and become unmanaged.
272
366
  - `patch reserve <order> --category <c> --owner <label> [--reason <text>]`
273
367
  holds an order for a patch a session has agreed to export but has not
274
368
  exported yet. The reservation lives in `patches.json#reserved`. Like a
@@ -289,22 +383,35 @@ hash-object` and `git update-index`. A collision (two rows at one order) or
289
383
  an invalid result is refused before the index is touched. The working copy
290
384
  is never changed and nothing is committed. Name a patch the working copy
291
385
  no longer has by its HEAD filename to stage its removal, and name both
292
- filenames of a renamed patch.
386
+ filenames of a renamed patch. Every manifest write, a
387
+ re-export's row update included, puts every row, changed or not, in the one
388
+ canonical key order that `patch stage` writes, so after a write the working
389
+ copy and the index do not differ on key order alone.
293
390
  - `patch staged-dependency <name> --remove` names the entries to remove by
294
391
  any of `--file`, `--specifier`/`--line`, `--creates` and `--owner`. It needs
295
- `--creates`, or `--file` with the matcher. `--creates` alone removes every
296
- declaration for that created path, which is the cleanup after a merge
297
- deletes it. Registration lines compare whitespace-insensitively, as the
298
- lint does. A selection that matches nothing is refused with exit 8 (it used
299
- to print "no staged registration matched" and exit 0), on a dry run too.
392
+ `--creates`, or `--file` with the matcher. Without `--kind`, `--specifier`
393
+ selects imports, `--line` selects registrations, and a selection with
394
+ neither searches both kinds, so `--creates` alone removes every
395
+ declaration for that created path, imports and registrations alike, which
396
+ is the cleanup after a merge deletes it. Registration lines compare
397
+ whitespace-insensitively, as the lint does. A selection that matches
398
+ nothing is refused with exit 8, on a dry run too, and the refusal lists
399
+ the patch's declarations of every kind it searched.
300
400
  - `patch staged-dependency <name> --add --from <json-file>` adds many
301
401
  declarations in one call under one lock hold. The file is a `lint
302
402
  --per-patch --report` document (its findings' `stagedDeclarations` for
303
403
  that patch) or a JSON array of `{ file, specifier | line, creates, owner?,
304
404
  reason?, patch? }` entries; entries naming another patch are skipped, and
305
405
  `--reason` fills entries without one. Every `staged-dependency` call logs
306
- only its delta (`added` / `removed`) in the history; records written
307
- before 0.50.0 carry the whole `before` / `after` lists instead.
406
+ only its delta (`added` / `removed`) in the history. A history record may
407
+ carry the whole `before` / `after` lists instead.
408
+ - `--add` (single or `--from`) on a declaration already present
409
+ (same file, specifier or line, creates and owner) with a different
410
+ `reason` replaces the reason in place, keeping the entry's position in its
411
+ list. The summary counts these apart from additions, as in
412
+ `updated the reason of 87`, and the history logs each as the old entry
413
+ removed and the new one added. An `--add` without a reason never clears
414
+ one.
308
415
 
309
416
  ### Parking a session's edits
310
417
 
@@ -332,7 +439,10 @@ resolves.
332
439
 
333
440
  `lint --per-patch --patches` accepts repeated flags, comma lists, full
334
441
  filenames or stems, bare order numbers, manifest names, category-prefixed
335
- slugs, and bare slugs.
442
+ slugs, and bare slugs. A list is also split on whitespace, so
443
+ a list held in one shell variable (`--patches "$P"` under zsh) selects every
444
+ patch it names. A name that matches no patch is refused with that name, its
445
+ nearest matches and the queue size, not with every patch in the queue.
336
446
 
337
447
  `lint --per-patch --max-warnings 0` is the warning-clean form for a release
338
448
  gate.
@@ -341,8 +451,8 @@ gate.
341
451
  every NOTICE row, for example `NOTICE [large-patch-lines]: 180 across 172
342
452
  patch(es), 150 of them suppressed by lintIgnore (see --report)`. On a green
343
453
  queue those rows are reviewed waivers and under-threshold measurements, and
344
- there can be hundreds of them. The default, `--notices full`, prints them
345
- as before. Errors and warnings always print in full, and the `--report`
454
+ there can be hundreds of them. The default, `--notices full`, prints every
455
+ row. Errors and warnings always print in full, and the `--report`
346
456
  JSON is the same in both modes.
347
457
 
348
458
  `lint --per-patch --report <path>` writes a machine-readable JSON report with
@@ -361,7 +471,7 @@ programmatic API, with the file-size helpers `isTestFile`,
361
471
  `file-too-large` measures in a patch: the JS files it creates, and never a
362
472
  modified file or a new file of another kind. The rule selects its files
363
473
  through that same function, so an audit that uses it cannot drift from the
364
- rule. Since 0.51.0 the API also exports the measurement the size rules
474
+ rule. The API also exports the measurement the size rules
365
475
  themselves take, `measurePatchSize(engineDir, diff, filesAffected, config)`
366
476
  (lines, test-file lines, files and registration files under the configured
367
477
  metric), `isPatchSizeRegistrationFile(file, globs)`, the per-line
@@ -391,8 +501,8 @@ the real type. See
391
501
  [`configuration.md`](configuration.md#patchlint) for the project hooks
392
502
  (`checkJsExtraShim`, `checkJsTestFiles`, `checkJsTestShim`).
393
503
 
394
- For release whitespace checks, use `npm run whitespace:check`. It still
395
- checks source diffs while excluding the diff syntax of generated
504
+ For release whitespace checks, use `npm run whitespace:check`. It checks
505
+ source diffs while excluding the diff syntax of generated
396
506
  `patches/*.patch` files.
397
507
 
398
508
  ### Linting single files
@@ -515,13 +625,14 @@ On a shared engine, `--patches <list>` judges only the named patches:
515
625
  `status` keeps the files each one owns or claims (and, with `--ownership` or
516
626
  the ownership checks, the rows naming it), and `verify` keeps the findings
517
627
  that name it. Patches are named the way `lint --per-patch --patches` names
518
- them (filename, stem, order number, manifest name or slug). A peer's drifted
519
- patch then no longer turns this session's gate red. The limit is the
628
+ them (filename, stem, order number, manifest name or slug; repeated, or
629
+ separated by commas or whitespace). A peer's drifted
630
+ patch then does not turn this session's gate red. The limit is the
520
631
  point: an unmanaged file belongs to no patch, so a scoped run never judges
521
632
  unexported files, this session's included. Run the unscoped gate before a
522
633
  commit that should cover them. `lint --per-patch --patches` keeps every
523
634
  queue finding that names a scoped patch, even on a file the patch no longer
524
- touches (a stale `staged-dependency-unused` declaration, since 0.51.0). `verify --patches` keeps findings about the
635
+ touches (a stale `staged-dependency-unused` declaration). `verify --patches` keeps findings about the
525
636
  queue as a whole (a `patches.json` that does not parse) whatever the scope.
526
637
 
527
638
  `verify` resolves every `resource:///modules/…` and `resource://gre/modules/…`
@@ -586,7 +697,7 @@ as possibly cascading from it, since the replay continues without the
586
697
  rejected patch. Run it after the download, when HEAD is the new source. The
587
698
  header names the previous pin as "from": `firefox.version` in the
588
699
  `fireforge.json` committed at the project's HEAD, the tree the queue was
589
- last applied to (since 0.50.0). The patch stamps follow on their own line
700
+ last applied to. The patch stamps follow on their own line
590
701
  (`Patch stamps: 317 at 153.0esr, 5 at 153.3.0esr`), oldest first and
591
702
  compared as versions rather than strings, because patches untouched since an
592
703
  older hop keep their older stamp. When no committed pin precedes the target
package/docs/run-logs.md CHANGED
@@ -8,16 +8,15 @@
8
8
 
9
9
  ## Why the tool holds the log, not the operator
10
10
 
11
- A run's diagnosis used to exist in exactly one place: the terminal. The
12
- convenient thing to do when output is long is
11
+ Without a log, a run's diagnosis exists in exactly one place: the terminal.
12
+ The convenient thing to do when output is long is
13
13
  `fireforge test … 2>&1 | tail -40`, which keeps the summary, discards the
14
14
  `TEST-UNEXPECTED-FAIL` lines that say what broke, and launders the exit code
15
- as well. Nothing else held the output: `runMachCapture` keeps a 2 MB
15
+ as well. Nothing else holds the output: `runMachCapture` keeps a 2 MB
16
16
  in-memory tail that dies with the process, and the shard retry loop discards
17
17
  earlier attempts outright.
18
18
 
19
- The operator rule ("never pipe a run") was written down three times
20
- downstream and broken after each writing. That is a sign it is not an
19
+ An operator rule ("never pipe a run") gets broken, so this is not an
21
20
  operator problem. The tool is the only party that can make the log survive
22
21
  the mistake.
23
22
 
@@ -25,7 +24,7 @@ the mistake.
25
24
 
26
25
  - **Complete output, never the echo filter's collapsed form.** The artifact
27
26
  exists so that whoever is diagnosing the run can re-read it. Shortening it
28
- would bring back the loss it prevents. The one transformation applied is
27
+ would lose what it is kept for. The one transformation applied is
29
28
  the secret-masking pass below, which changes values but never line
30
29
  structure.
31
30
  - **Secret-shaped values are masked in the file, and only in the file.** The
@@ -94,11 +93,11 @@ away. The refusal's own explanatory text is written into the log, and to
94
93
  stdout, before the verdict line seals stdout.
95
94
 
96
95
  Both halves matter. The verdict seal routes everything the CLI error boundary
97
- renders afterwards to stderr, so a run captured with `> file` used to keep
96
+ renders afterwards to stderr, so a run captured with `> file` would keep
98
97
  `FIREFORGE-VERDICT: FAIL reason=preflight` and nothing else. That rendering
99
98
  also happens after the run log is closed, so the artifact the verdict's own
100
- `log=` key pointed at held only the pre-test build. Writing before the seal
101
- fixes both without giving up the contract: the reason lands on both channels,
99
+ `log=` key points at would hold only the pre-test build. Writing before the
100
+ seal avoids both without giving up the contract: the reason lands on both channels,
102
101
  and the verdict is still the run's last stdout line.
103
102
 
104
103
  ## Where the path is announced