@intentius/chant 0.124.30 → 0.124.32

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 (346) hide show
  1. package/bin/chant +7 -2
  2. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  3. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  4. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  5. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  6. package/dist/cli/commands/check-lexicon.js +18 -12
  7. package/dist/cli/commands/import.d.ts +10 -0
  8. package/dist/cli/commands/import.d.ts.map +1 -1
  9. package/dist/cli/commands/import.js +37 -0
  10. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  11. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  12. package/dist/cli/commands/onboard.d.ts.map +1 -1
  13. package/dist/cli/commands/onboard.js +8 -4
  14. package/dist/cli/commands/update.d.ts.map +1 -1
  15. package/dist/cli/commands/update.js +2 -1
  16. package/dist/cli/dependencies.d.ts +18 -0
  17. package/dist/cli/dependencies.d.ts.map +1 -0
  18. package/dist/cli/dependencies.js +120 -0
  19. package/dist/cli/handlers/build.d.ts.map +1 -1
  20. package/dist/cli/handlers/build.js +23 -4
  21. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  22. package/dist/cli/handlers/fan-out.js +12 -4
  23. package/dist/cli/handlers/graph.d.ts.map +1 -1
  24. package/dist/cli/handlers/graph.js +16 -6
  25. package/dist/cli/handlers/lint.d.ts.map +1 -1
  26. package/dist/cli/handlers/lint.js +6 -0
  27. package/dist/cli/handlers/operator.d.ts +2 -2
  28. package/dist/cli/handlers/operator.js +3 -3
  29. package/dist/cli/handlers/pr.d.ts +14 -0
  30. package/dist/cli/handlers/pr.d.ts.map +1 -1
  31. package/dist/cli/handlers/pr.js +488 -32
  32. package/dist/cli/handlers/promote.d.ts.map +1 -1
  33. package/dist/cli/handlers/promote.js +3 -1
  34. package/dist/cli/handlers/run.d.ts +13 -1
  35. package/dist/cli/handlers/run.d.ts.map +1 -1
  36. package/dist/cli/handlers/run.js +38 -15
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +74 -12
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +65 -0
  41. package/dist/cli/registry.d.ts +29 -2
  42. package/dist/cli/registry.d.ts.map +1 -1
  43. package/dist/cli/registry.js +6 -2
  44. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  45. package/dist/codegen/lexicon-regen.d.ts +4 -0
  46. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  47. package/dist/codegen/lexicon-regen.js +1 -1
  48. package/dist/codegen/pinned-upgrade.js +2 -2
  49. package/dist/components/capability.d.ts +44 -3
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/capability.js +21 -0
  52. package/dist/components/changed-files.d.ts +7 -1
  53. package/dist/components/changed-files.d.ts.map +1 -1
  54. package/dist/components/changed-files.js +9 -1
  55. package/dist/components/driver.d.ts +38 -0
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/driver.js +47 -6
  58. package/dist/components/fan-out-record.d.ts +6 -1
  59. package/dist/components/fan-out-record.d.ts.map +1 -1
  60. package/dist/components/fan-out-record.js +24 -0
  61. package/dist/components/fan-out-run.d.ts +20 -0
  62. package/dist/components/fan-out-run.d.ts.map +1 -1
  63. package/dist/components/fan-out-run.js +21 -1
  64. package/dist/components/pr-pipeline.d.ts +65 -2
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +107 -15
  67. package/dist/components/pr-run.d.ts +127 -29
  68. package/dist/components/pr-run.d.ts.map +1 -1
  69. package/dist/components/pr-run.js +290 -70
  70. package/dist/components/wave-plan.d.ts +5 -2
  71. package/dist/components/wave-plan.d.ts.map +1 -1
  72. package/dist/components/wave-plan.js +12 -1
  73. package/dist/discovery/fold-import.d.ts.map +1 -1
  74. package/dist/discovery/fold-import.js +16 -6
  75. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  76. package/dist/discovery/sandbox/bundle.js +6 -0
  77. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  78. package/dist/discovery/specifier-resolve.js +8 -2
  79. package/dist/fold/fold.d.ts.map +1 -1
  80. package/dist/fold/fold.js +68 -1
  81. package/dist/fold/subset.d.ts +37 -0
  82. package/dist/fold/subset.d.ts.map +1 -1
  83. package/dist/fold/subset.js +88 -0
  84. package/dist/lexicon-module.d.ts +10 -0
  85. package/dist/lexicon-module.d.ts.map +1 -1
  86. package/dist/lexicon-module.js +53 -8
  87. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  88. package/dist/lexicon-plugin-helpers.js +6 -4
  89. package/dist/lexicon.d.ts +2 -0
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/affected.d.ts.map +1 -1
  92. package/dist/lifecycle/affected.js +7 -1
  93. package/dist/lifecycle/release-ledger.d.ts +21 -0
  94. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  95. package/dist/lifecycle/release-ledger.js +24 -0
  96. package/dist/lifecycle/work-dir.d.ts +45 -0
  97. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  98. package/dist/lifecycle/work-dir.js +78 -0
  99. package/dist/lint/discover.d.ts +17 -9
  100. package/dist/lint/discover.d.ts.map +1 -1
  101. package/dist/lint/discover.js +62 -54
  102. package/dist/module-source.d.ts +57 -0
  103. package/dist/module-source.d.ts.map +1 -0
  104. package/dist/module-source.js +104 -0
  105. package/dist/op/activities/factory.js +1 -1
  106. package/dist/op/discover.d.ts.map +1 -1
  107. package/dist/op/discover.js +13 -3
  108. package/dist/op/gate.d.ts.map +1 -1
  109. package/dist/op/gate.js +3 -0
  110. package/dist/op/local-executor.d.ts +12 -0
  111. package/dist/op/local-executor.d.ts.map +1 -1
  112. package/dist/op/local-executor.js +22 -3
  113. package/dist/op/op-waves-run.d.ts.map +1 -1
  114. package/dist/op/op-waves-run.js +20 -5
  115. package/dist/op/operator.d.ts +2 -2
  116. package/dist/op/operator.js +2 -2
  117. package/dist/op/redact.d.ts +34 -0
  118. package/dist/op/redact.d.ts.map +1 -0
  119. package/dist/op/redact.js +94 -0
  120. package/dist/op/step-console.d.ts +2 -0
  121. package/dist/op/step-console.d.ts.map +1 -1
  122. package/dist/op/step-console.js +5 -2
  123. package/dist/op/steward.d.ts +6 -4
  124. package/dist/op/steward.d.ts.map +1 -1
  125. package/dist/op/steward.js +5 -3
  126. package/dist/op/work-lease-run.d.ts +20 -7
  127. package/dist/op/work-lease-run.d.ts.map +1 -1
  128. package/dist/op/work-lease-run.js +56 -12
  129. package/dist/pr-forge.d.ts +27 -0
  130. package/dist/pr-forge.d.ts.map +1 -1
  131. package/dist/pr-forge.js +51 -6
  132. package/dist/pr-loop.d.ts +141 -4
  133. package/dist/pr-loop.d.ts.map +1 -1
  134. package/dist/pr-loop.js +122 -6
  135. package/dist/pr-report-url.d.ts +10 -2
  136. package/dist/pr-report-url.d.ts.map +1 -1
  137. package/dist/pr-report-url.js +52 -6
  138. package/dist/ts-loader.d.ts +38 -4
  139. package/dist/ts-loader.d.ts.map +1 -1
  140. package/dist/ts-loader.js +190 -16
  141. package/dist/wave-runtime.mjs +208 -208
  142. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  143. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  144. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  145. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  146. package/dist/workspace/chant-migrations.js +2 -0
  147. package/dist/workspace/checks/boxes.d.ts +6 -6
  148. package/dist/workspace/checks/boxes.js +9 -9
  149. package/dist/workspace/ci-green-forge.d.ts +6 -4
  150. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  151. package/dist/workspace/ci-green.d.ts +28 -12
  152. package/dist/workspace/ci-green.d.ts.map +1 -1
  153. package/dist/workspace/ci-green.js +49 -21
  154. package/dist/workspace/ci-list.schema.json +12 -6
  155. package/dist/workspace/ci-report-url.d.ts +18 -0
  156. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  157. package/dist/workspace/ci-report-url.js +33 -0
  158. package/dist/workspace/ci-run-index.d.ts +108 -0
  159. package/dist/workspace/ci-run-index.d.ts.map +1 -0
  160. package/dist/workspace/ci-run-index.js +164 -0
  161. package/dist/workspace/ci-run-index.schema.json +52 -0
  162. package/dist/workspace/ci-run.d.ts +159 -9
  163. package/dist/workspace/ci-run.d.ts.map +1 -1
  164. package/dist/workspace/ci-run.js +419 -50
  165. package/dist/workspace/ci-runner-key.d.ts +133 -0
  166. package/dist/workspace/ci-runner-key.d.ts.map +1 -0
  167. package/dist/workspace/ci-runner-key.js +237 -0
  168. package/dist/workspace/ci-secrets.d.ts +65 -0
  169. package/dist/workspace/ci-secrets.d.ts.map +1 -0
  170. package/dist/workspace/ci-secrets.js +211 -0
  171. package/dist/workspace/ci-serve.d.ts +67 -4
  172. package/dist/workspace/ci-serve.d.ts.map +1 -1
  173. package/dist/workspace/ci-serve.js +240 -35
  174. package/dist/workspace/declaration.d.ts +8 -0
  175. package/dist/workspace/declaration.d.ts.map +1 -1
  176. package/dist/workspace/declaration.js +11 -2
  177. package/dist/workspace/declaration.schema.json +7 -2
  178. package/dist/workspace/export.d.ts +1 -1
  179. package/dist/workspace/export.d.ts.map +1 -1
  180. package/dist/workspace/export.js +7 -3
  181. package/dist/workspace/ls.d.ts +6 -1
  182. package/dist/workspace/ls.d.ts.map +1 -1
  183. package/dist/workspace/ls.js +1 -0
  184. package/dist/workspace/ls.schema.json +7 -1
  185. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  186. package/dist/workspace/pin-integrity.js +3 -2
  187. package/dist/workspace/pr-report.schema.json +74 -7
  188. package/dist/workspace/reason-codes.d.ts +7 -3
  189. package/dist/workspace/reason-codes.d.ts.map +1 -1
  190. package/dist/workspace/reason-codes.js +9 -4
  191. package/dist/workspace/run-event.schema.json +3 -1
  192. package/dist/workspace/status.schema.json +3 -1
  193. package/dist/workspace/trust/trust-write.d.ts +109 -0
  194. package/dist/workspace/trust/trust-write.d.ts.map +1 -0
  195. package/dist/workspace/trust/trust-write.js +320 -0
  196. package/dist/workspace/trust-write.schema.json +265 -0
  197. package/dist/workspace/write-scope.d.ts +9 -0
  198. package/dist/workspace/write-scope.d.ts.map +1 -1
  199. package/dist/workspace/write-scope.js +11 -1
  200. package/package.json +1 -1
  201. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  202. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  203. package/src/cli/commands/audit.test.ts +2 -2
  204. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  205. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  206. package/src/cli/commands/check-lexicon.test.ts +1 -1
  207. package/src/cli/commands/check-lexicon.ts +16 -11
  208. package/src/cli/commands/import.ts +40 -0
  209. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  210. package/src/cli/commands/init-lexicon.test.ts +8 -5
  211. package/src/cli/commands/onboard.test.ts +45 -0
  212. package/src/cli/commands/onboard.ts +8 -4
  213. package/src/cli/commands/update.test.ts +6 -0
  214. package/src/cli/commands/update.ts +4 -1
  215. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  216. package/src/cli/dependencies.e2e.test.ts +101 -0
  217. package/src/cli/dependencies.test.ts +93 -0
  218. package/src/cli/dependencies.ts +122 -0
  219. package/src/cli/handlers/build.test.ts +69 -0
  220. package/src/cli/handlers/build.ts +20 -4
  221. package/src/cli/handlers/fan-out.test.ts +73 -0
  222. package/src/cli/handlers/fan-out.ts +11 -4
  223. package/src/cli/handlers/graph.test.ts +76 -0
  224. package/src/cli/handlers/graph.ts +19 -12
  225. package/src/cli/handlers/lint.test.ts +42 -0
  226. package/src/cli/handlers/lint.ts +6 -0
  227. package/src/cli/handlers/operator.ts +3 -3
  228. package/src/cli/handlers/pr-waves.test.ts +449 -0
  229. package/src/cli/handlers/pr.test.ts +134 -0
  230. package/src/cli/handlers/pr.ts +523 -31
  231. package/src/cli/handlers/promote.test.ts +26 -1
  232. package/src/cli/handlers/promote.ts +3 -2
  233. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  234. package/src/cli/handlers/run-signer.test.ts +79 -0
  235. package/src/cli/handlers/run.ts +34 -16
  236. package/src/cli/main.test.ts +19 -0
  237. package/src/cli/main.ts +73 -12
  238. package/src/cli/parse-args.ts +48 -0
  239. package/src/cli/registry.ts +35 -4
  240. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  241. package/src/codegen/lexicon-regen.ts +1 -1
  242. package/src/codegen/pinned-upgrade.ts +2 -2
  243. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  244. package/src/components/capability.ts +51 -3
  245. package/src/components/changed-files.test.ts +16 -1
  246. package/src/components/changed-files.ts +9 -1
  247. package/src/components/driver.test.ts +36 -1
  248. package/src/components/driver.ts +76 -4
  249. package/src/components/fan-out-record.ts +29 -1
  250. package/src/components/fan-out-run.test.ts +35 -0
  251. package/src/components/fan-out-run.ts +40 -1
  252. package/src/components/pr-pipeline.test.ts +100 -0
  253. package/src/components/pr-pipeline.ts +132 -17
  254. package/src/components/pr-run.test.ts +323 -18
  255. package/src/components/pr-run.ts +354 -75
  256. package/src/components/wave-plan.ts +16 -4
  257. package/src/discovery/fold-host-method.test.ts +87 -0
  258. package/src/discovery/fold-import.ts +15 -5
  259. package/src/discovery/fold-package-composite.test.ts +27 -0
  260. package/src/discovery/fold-specifiers.test.ts +8 -0
  261. package/src/discovery/sandbox/bundle.ts +6 -0
  262. package/src/discovery/specifier-resolve.ts +8 -2
  263. package/src/fold/fold.test.ts +72 -0
  264. package/src/fold/fold.ts +71 -0
  265. package/src/fold/subset.test.ts +31 -0
  266. package/src/fold/subset.ts +99 -0
  267. package/src/lexicon-dist.e2e.test.ts +254 -0
  268. package/src/lexicon-module.test.ts +47 -0
  269. package/src/lexicon-module.ts +49 -7
  270. package/src/lexicon-plugin-helpers.ts +6 -4
  271. package/src/lexicon.ts +2 -0
  272. package/src/lifecycle/affected.test.ts +7 -0
  273. package/src/lifecycle/affected.ts +6 -1
  274. package/src/lifecycle/release-ledger.ts +34 -0
  275. package/src/lifecycle/work-dir.test.ts +77 -0
  276. package/src/lifecycle/work-dir.ts +82 -0
  277. package/src/lint/discover.test.ts +46 -2
  278. package/src/lint/discover.ts +55 -49
  279. package/src/module-source.test.ts +85 -0
  280. package/src/module-source.ts +112 -0
  281. package/src/op/activities/factory.ts +1 -1
  282. package/src/op/discover-root.test.ts +31 -2
  283. package/src/op/discover.ts +14 -4
  284. package/src/op/gate.test.ts +30 -0
  285. package/src/op/gate.ts +3 -0
  286. package/src/op/local-executor.ts +31 -3
  287. package/src/op/op-waves-run.ts +20 -5
  288. package/src/op/op-waves.test.ts +49 -0
  289. package/src/op/operator.ts +2 -2
  290. package/src/op/redact.ts +93 -0
  291. package/src/op/step-console.ts +7 -2
  292. package/src/op/steward.ts +6 -4
  293. package/src/op/work-lease-run.test.ts +88 -2
  294. package/src/op/work-lease-run.ts +52 -12
  295. package/src/pr-forge-bundle.test.ts +2 -2
  296. package/src/pr-forge.test.ts +71 -0
  297. package/src/pr-forge.ts +70 -6
  298. package/src/pr-loop-bundle.test.ts +2 -1
  299. package/src/pr-loop.test.ts +117 -1
  300. package/src/pr-loop.ts +252 -10
  301. package/src/pr-report-url.ts +58 -6
  302. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  303. package/src/ts-loader.test.ts +89 -1
  304. package/src/ts-loader.ts +180 -13
  305. package/src/workspace/changes.test.ts +1 -1
  306. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  307. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  308. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  309. package/src/workspace/chant-migrations.ts +2 -0
  310. package/src/workspace/checks/boxes.ts +9 -9
  311. package/src/workspace/ci-green-forge.ts +6 -4
  312. package/src/workspace/ci-green-runs.test.ts +53 -11
  313. package/src/workspace/ci-green.test.ts +1 -1
  314. package/src/workspace/ci-green.ts +50 -22
  315. package/src/workspace/ci-list.schema.json +12 -6
  316. package/src/workspace/ci-report-url.test.ts +220 -0
  317. package/src/workspace/ci-report-url.ts +33 -0
  318. package/src/workspace/ci-run-index.schema.json +52 -0
  319. package/src/workspace/ci-run-index.test.ts +392 -0
  320. package/src/workspace/ci-run-index.ts +206 -0
  321. package/src/workspace/ci-run.test.ts +520 -42
  322. package/src/workspace/ci-run.ts +483 -51
  323. package/src/workspace/ci-runner-key.test.ts +204 -0
  324. package/src/workspace/ci-runner-key.ts +275 -0
  325. package/src/workspace/ci-secrets.test.ts +106 -0
  326. package/src/workspace/ci-secrets.ts +213 -0
  327. package/src/workspace/ci-serve.test.ts +282 -39
  328. package/src/workspace/ci-serve.ts +231 -34
  329. package/src/workspace/declaration.schema.json +7 -2
  330. package/src/workspace/declaration.test.ts +23 -0
  331. package/src/workspace/declaration.ts +19 -3
  332. package/src/workspace/export.ts +6 -3
  333. package/src/workspace/ls.schema.json +7 -1
  334. package/src/workspace/ls.ts +7 -2
  335. package/src/workspace/pin-integrity.test.ts +5 -0
  336. package/src/workspace/pin-integrity.ts +3 -2
  337. package/src/workspace/pr-report.schema.json +74 -7
  338. package/src/workspace/reason-codes.test.ts +2 -0
  339. package/src/workspace/reason-codes.ts +9 -4
  340. package/src/workspace/run-event.schema.json +3 -1
  341. package/src/workspace/status.schema.json +3 -1
  342. package/src/workspace/trust/trust-write.test.ts +179 -0
  343. package/src/workspace/trust/trust-write.ts +377 -0
  344. package/src/workspace/trust-write.schema.json +265 -0
  345. package/src/workspace/write-scope.test.ts +29 -1
  346. package/src/workspace/write-scope.ts +12 -1
@@ -15,12 +15,34 @@
15
15
  * the one `chant operator` holds) named for `prApplyGroup`, renews it while
16
16
  * it runs and releases it after. An apply that finds it held waits for a
17
17
  * later poll, as a forge's concurrency group queues it.
18
+ * - Merged pull request. An apply asks the forge for the pull request whose
19
+ * merge commit the new head is (`pullRequestFor`, as pr-apply does) and
20
+ * passes it as `--pr`, so its commit status link and run index use
21
+ * `<pr>/<commit>`. With none found it runs without `--pr`.
18
22
  * - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
19
23
  * as its `--resume` record, so a re-run of a failed apply at the same
20
24
  * commit finishes under the approval it has. This replaces the forge's CI
21
25
  * cache.
26
+ * - A gate per wave (#4003). With `--wave-gate` the apply job runs
27
+ * `pr-apply --wave-gate`, which applies the waves in order and stops at
28
+ * the first whose moved plan waits on its own gate. The job fails, and the
29
+ * resume record keeps the waves that applied; once the wave's plan is
30
+ * approved, `rerunFailed` runs the apply again and it carries on from
31
+ * that wave.
22
32
  * - Capacity. At most `maxJobs` jobs run at once. A job over the limit is not
23
33
  * recorded, so a later poll starts it.
34
+ * - Fast-forwards only (#4122). An apply runs only when the target's new head
35
+ * descends from the head seen before it. Any other move, such as a force
36
+ * push back to an older commit, is recorded as seen and not applied.
37
+ * - `[skip ci]` (#4122). A target head or pull request head whose commit
38
+ * message says `[skip ci]` or `[ci skip]` is recorded as seen and not run,
39
+ * as Actions does.
40
+ * - The forge's ledger (#4121). Before each job, when no other job of this
41
+ * serve runs, the clone's `chant/lifecycle` follows the forge's: it is
42
+ * deleted when the forge has none, and reset to the forge's when neither
43
+ * contains the other. Each is a warning. A local branch ahead of the
44
+ * forge's keeps its facts, and the job's push sends them. Nothing here
45
+ * pushes the ledger.
24
46
  *
25
47
  * Host mode only: each job runs in a fresh worktree on this host as this
26
48
  * user, with the clean environment `ciRun` gives it. A pull request from a
@@ -40,6 +62,8 @@ export type CiServeForgeKind = "github" | "forgejo";
40
62
  export interface CiServeForge {
41
63
  openPullRequests(base: string): Promise<OpenPullRequest[]>;
42
64
  branchHead(branch: string): Promise<string | undefined>;
65
+ /** The merged pull request that produced a commit, as pr-apply finds it (`pullRequestFor`). Without it an apply gets no `--pr`. */
66
+ pullRequestFor?(sha: string): Promise<number | null>;
43
67
  }
44
68
  /** A job the forge's state calls for: the plan of a pull request's head, or the apply of a target-branch commit. */
45
69
  export type CiServeTrigger = {
@@ -79,9 +103,11 @@ export interface CiServeState {
79
103
  env: string;
80
104
  branch: string;
81
105
  member?: string;
82
- /** The target branch's head the last apply was measured to, or the head the first poll found. */
106
+ /** The target branch's head the last apply was measured to, or the last head seen and not applied. */
83
107
  target?: string;
84
108
  jobs: Record<string, CiServeJobState>;
109
+ /** Plans seen and not run, by state key, with why (#4122). */
110
+ skipped?: Record<string, "skip-ci">;
85
111
  }
86
112
  /** What a pass did, in order, for the log. */
87
113
  export type CiServeEvent = {
@@ -119,6 +145,19 @@ export type CiServeEvent = {
119
145
  status: "ok" | "fail";
120
146
  failure?: string;
121
147
  report?: string;
148
+ } | {
149
+ type: "not-run";
150
+ job: CiJobName;
151
+ commit: string;
152
+ pr?: number;
153
+ reason: "not-fast-forward" | "skip-ci";
154
+ branch?: string;
155
+ from?: string;
156
+ } | {
157
+ type: "ledger";
158
+ action: "deleted" | "reset";
159
+ local: string;
160
+ forge?: string;
122
161
  } | {
123
162
  type: "error";
124
163
  message: string;
@@ -162,17 +201,34 @@ export declare function ciServeKey(t: {
162
201
  commit: string;
163
202
  pr?: number;
164
203
  }): string;
204
+ /**
205
+ * Make the clone's `chant/lifecycle` follow the forge's (#4121). The clone's
206
+ * branch outlives the forge's, so a ledger the forge deleted or rewrote would
207
+ * make the next pr-apply find the two diverged and stall.
208
+ *
209
+ * - The forge has none: the local branch and its remote-tracking ref go.
210
+ * - Neither contains the other: the local branch and its remote-tracking ref
211
+ * are reset to the forge's tip.
212
+ * - The local branch is ahead of the forge's, or behind it, or the same: it
213
+ * is left, and an apply's push sends what it holds that the forge lacks.
214
+ *
215
+ * Nothing is pushed, so a rewritten ledger never goes back to the forge. A
216
+ * forge that can't be reached leaves everything as it is; the job's own
217
+ * fetch says why.
218
+ */
219
+ export declare function reconcileCiServeLedger(top: string): CiServeEvent | undefined;
165
220
  /** Where a serve keeps its state when `--state` names nowhere: beside the run events, in the repository's common git directory. */
166
221
  export declare function defaultCiServeStateDir(cwd: string): string;
167
222
  /** The state file, or a new one. */
168
223
  export declare function readCiServeState(file: string, fresh: Omit<CiServeState, "schema" | "jobs">): CiServeState;
169
224
  /**
170
225
  * One poll of the forge, as triggers: an apply when the target's head moved
171
- * since the last apply (none on the first poll, which only notes the head),
226
+ * since the last apply by a fast-forward (none on the first poll, which only
227
+ * notes the head, and none for a head whose message says `[skip ci]`),
172
228
  * then a plan for each open pull request head not yet planned, oldest pull
173
229
  * request first. A job already in the state file is left out unless it
174
- * failed and `rerunFailed` is set. The first poll's head is written to the
175
- * state file.
230
+ * failed and `rerunFailed` is set. The first poll's head, and a head seen
231
+ * and not applied, are written to the state file.
176
232
  */
177
233
  export declare function ciServeObserve(opts: CiServeOptions, rt?: CiServeRuntime): Promise<{
178
234
  triggers: CiServeTrigger[];
@@ -207,6 +263,13 @@ export declare function ciServeLoop(opts: CiServeOptions & {
207
263
  }): Promise<void>;
208
264
  /** One line for an event, as `ci serve` prints it. */
209
265
  export declare function formatCiServeEvent(e: CiServeEvent): string;
266
+ /**
267
+ * `chant ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]` (#4099):
268
+ * make the host's runner key when there is none, print its public half on
269
+ * stdout, and say whether the target's head lists it. With --trust, commit
270
+ * the runner entry on a branch of its own and push it for review.
271
+ */
272
+ export declare function runnerKeySetupCommand(ctx: CommandContext): number;
210
273
  /** `--interval`: seconds, or a duration such as `30s` or `2m`. Default 30 seconds. */
211
274
  export declare function ciServeInterval(value: string | undefined): number;
212
275
  /** `chant ci serve` (#4095). */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"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"}
@@ -15,12 +15,34 @@
15
15
  * the one `chant operator` holds) named for `prApplyGroup`, renews it while
16
16
  * it runs and releases it after. An apply that finds it held waits for a
17
17
  * later poll, as a forge's concurrency group queues it.
18
+ * - Merged pull request. An apply asks the forge for the pull request whose
19
+ * merge commit the new head is (`pullRequestFor`, as pr-apply does) and
20
+ * passes it as `--pr`, so its commit status link and run index use
21
+ * `<pr>/<commit>`. With none found it runs without `--pr`.
18
22
  * - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
19
23
  * as its `--resume` record, so a re-run of a failed apply at the same
20
24
  * commit finishes under the approval it has. This replaces the forge's CI
21
25
  * cache.
26
+ * - A gate per wave (#4003). With `--wave-gate` the apply job runs
27
+ * `pr-apply --wave-gate`, which applies the waves in order and stops at
28
+ * the first whose moved plan waits on its own gate. The job fails, and the
29
+ * resume record keeps the waves that applied; once the wave's plan is
30
+ * approved, `rerunFailed` runs the apply again and it carries on from
31
+ * that wave.
22
32
  * - Capacity. At most `maxJobs` jobs run at once. A job over the limit is not
23
33
  * recorded, so a later poll starts it.
34
+ * - Fast-forwards only (#4122). An apply runs only when the target's new head
35
+ * descends from the head seen before it. Any other move, such as a force
36
+ * push back to an older commit, is recorded as seen and not applied.
37
+ * - `[skip ci]` (#4122). A target head or pull request head whose commit
38
+ * message says `[skip ci]` or `[ci skip]` is recorded as seen and not run,
39
+ * as Actions does.
40
+ * - The forge's ledger (#4121). Before each job, when no other job of this
41
+ * serve runs, the clone's `chant/lifecycle` follows the forge's: it is
42
+ * deleted when the forge has none, and reset to the forge's when neither
43
+ * contains the other. Each is a warning. A local branch ahead of the
44
+ * forge's keeps its facts, and the job's push sends them. Nothing here
45
+ * pushes the ledger.
24
46
  *
25
47
  * Host mode only: each job runs in a fresh worktree on this host as this
26
48
  * user, with the clean environment `ciRun` gives it. A pull request from a
@@ -31,17 +53,19 @@
31
53
  * {@link ciServeObserve} is the poll that makes them; a webhook handler would
32
54
  * turn a checked payload into the same triggers and schedule them.
33
55
  */
34
- import { execFileSync } from "node:child_process";
56
+ import { execFileSync, spawnSync } from "node:child_process";
35
57
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
36
- import { isAbsolute, join, resolve } from "node:path";
58
+ import { join, resolve } from "node:path";
37
59
  import { prApplyGroup, prApplyRecordKey } from "../components/pr-pipeline.js";
38
60
  import { forgeFromEnv } from "../pr-forge.js";
39
61
  import { formatError, formatWarning } from "../cli/format.js";
40
62
  import { acquireLease, currentHolderId, releaseLease, DEFAULT_LEASE_TTL_MS } from "../lifecycle/lease.js";
41
63
  import { parseDuration } from "../op/duration.js";
42
- import { CI_JOB_OPS, ciJobPlace, ciRun, CiRunError } from "./ci-run.js";
43
- import { RUNNER_KEY_ENV } from "./ci-green.js";
64
+ import { reportUrlOf } from "./ci-report-url.js";
65
+ import { CI_JOB_OPS, ciJobPlace, ciLoopOptions, ciSecretsOf, ciRun, ciStatusFromEnv, CiRunError } from "./ci-run.js";
66
+ import { RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green.js";
44
67
  import { gitTop } from "./tree.js";
68
+ import { ciServeRunnerKey, formatRunnerKeySetup, setupRunnerKey } from "./ci-runner-key.js";
45
69
  export const newCiServeRuntime = () => ({ inflight: new Map() });
46
70
  /** The state key of a job. */
47
71
  export function ciServeKey(t) {
@@ -56,6 +80,79 @@ function tryGit(cwd, args) {
56
80
  return undefined;
57
81
  }
58
82
  }
83
+ const LEDGER = "chant/lifecycle";
84
+ /** Where the forge's ledger tip is fetched to, a ref only this module writes. */
85
+ const FORGE_LEDGER = "refs/chant/ci-serve/forge-ledger";
86
+ /** `git` with its exit status, for the calls whose answer is the status. */
87
+ function gitStatus(cwd, args) {
88
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, LC_ALL: "C" } });
89
+ return { code: r.status ?? -1, out: (r.stdout ?? "").trim() };
90
+ }
91
+ /**
92
+ * Make the clone's `chant/lifecycle` follow the forge's (#4121). The clone's
93
+ * branch outlives the forge's, so a ledger the forge deleted or rewrote would
94
+ * make the next pr-apply find the two diverged and stall.
95
+ *
96
+ * - The forge has none: the local branch and its remote-tracking ref go.
97
+ * - Neither contains the other: the local branch and its remote-tracking ref
98
+ * are reset to the forge's tip.
99
+ * - The local branch is ahead of the forge's, or behind it, or the same: it
100
+ * is left, and an apply's push sends what it holds that the forge lacks.
101
+ *
102
+ * Nothing is pushed, so a rewritten ledger never goes back to the forge. A
103
+ * forge that can't be reached leaves everything as it is; the job's own
104
+ * fetch says why.
105
+ */
106
+ export function reconcileCiServeLedger(top) {
107
+ const local = tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${LEDGER}^{commit}`]);
108
+ const listed = gitStatus(top, ["ls-remote", "--exit-code", "origin", `refs/heads/${LEDGER}`]);
109
+ if (listed.code === 2) {
110
+ // A remote-tracking ref left from a deleted branch would be the next push's lease, and refuse it.
111
+ tryGit(top, ["update-ref", "-d", `refs/remotes/origin/${LEDGER}`]);
112
+ if (!local)
113
+ return undefined;
114
+ tryGit(top, ["update-ref", "-d", `refs/heads/${LEDGER}`, local]);
115
+ return { type: "ledger", action: "deleted", local };
116
+ }
117
+ if (listed.code !== 0 || !local)
118
+ return undefined;
119
+ const forge = listed.out.split(/\s+/)[0];
120
+ if (!forge || forge === local)
121
+ return undefined;
122
+ if (gitStatus(top, ["fetch", "--quiet", "--no-tags", "origin", `+refs/heads/${LEDGER}:${FORGE_LEDGER}`]).code !== 0)
123
+ return undefined;
124
+ try {
125
+ const ahead = gitStatus(top, ["merge-base", "--is-ancestor", forge, local]).code === 0;
126
+ const behind = gitStatus(top, ["merge-base", "--is-ancestor", local, forge]).code === 0;
127
+ if (ahead || behind)
128
+ return undefined;
129
+ tryGit(top, ["update-ref", `refs/heads/${LEDGER}`, forge, local]);
130
+ tryGit(top, ["update-ref", `refs/remotes/origin/${LEDGER}`, forge]);
131
+ return { type: "ledger", action: "reset", local, forge };
132
+ }
133
+ finally {
134
+ tryGit(top, ["update-ref", "-d", FORGE_LEDGER]);
135
+ }
136
+ }
137
+ /** A commit message that asks for no CI run, as Actions and GitLab read it. */
138
+ const SKIP_CI = /\[(?:skip ci|ci skip)\]/i;
139
+ /** Whether `commit`'s message asks for no CI run. A commit this clone can't read is not skipped. */
140
+ function skipsCi(top, commit) {
141
+ const body = tryGit(top, ["log", "-1", "--format=%B", commit]);
142
+ return body !== undefined && SKIP_CI.test(body);
143
+ }
144
+ /**
145
+ * How the target branch moved from `from` to `head` (#4122): `fast-forward`
146
+ * when `head` descends from `from`, `other` when it does not, and `unknown`
147
+ * when `head` could not be fetched. Once `head` is here its history is too,
148
+ * so a `from` this clone lacks is not in that history.
149
+ */
150
+ function targetMove(top, branch, from, head) {
151
+ tryGit(top, ["fetch", "--quiet", "--no-tags", "origin", branch]);
152
+ if (gitStatus(top, ["cat-file", "-e", `${head}^{commit}`]).code !== 0)
153
+ return "unknown";
154
+ return gitStatus(top, ["merge-base", "--is-ancestor", from, head]).code === 0 ? "fast-forward" : "other";
155
+ }
59
156
  /** Where a serve keeps its state when `--state` names nowhere: beside the run events, in the repository's common git directory. */
60
157
  export function defaultCiServeStateDir(cwd) {
61
158
  const common = tryGit(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
@@ -93,15 +190,23 @@ function writeState(file, state) {
93
190
  const failedEntry = (entry, key, rt) => !!entry && (entry.status === "fail" || (entry.status === "running" && !rt.inflight.has(key)));
94
191
  /**
95
192
  * One poll of the forge, as triggers: an apply when the target's head moved
96
- * since the last apply (none on the first poll, which only notes the head),
193
+ * since the last apply by a fast-forward (none on the first poll, which only
194
+ * notes the head, and none for a head whose message says `[skip ci]`),
97
195
  * then a plan for each open pull request head not yet planned, oldest pull
98
196
  * request first. A job already in the state file is left out unless it
99
- * failed and `rerunFailed` is set. The first poll's head is written to the
100
- * state file.
197
+ * failed and `rerunFailed` is set. The first poll's head, and a head seen
198
+ * and not applied, are written to the state file.
101
199
  */
102
200
  export async function ciServeObserve(opts, rt = newCiServeRuntime()) {
103
201
  const place = servePlace(opts);
104
- const state = readCiServeState(place.stateFile, { forge: opts.forge, repo: opts.repo, env: opts.env, branch: place.branch, ...(place.member ? { member: place.member } : {}) });
202
+ const fresh = { forge: opts.forge, repo: opts.repo, env: opts.env, branch: place.branch, ...(place.member ? { member: place.member } : {}) };
203
+ const state = readCiServeState(place.stateFile, fresh);
204
+ // Read again at the write: a job of this serve may have settled while the forge answered.
205
+ const update = (fn) => {
206
+ const s = readCiServeState(place.stateFile, fresh);
207
+ fn(s);
208
+ writeState(place.stateFile, s);
209
+ };
105
210
  const triggers = [];
106
211
  const events = [];
107
212
  const [head, prs] = await Promise.all([opts.client.branchHead(place.branch), opts.client.openPullRequests(place.branch)]);
@@ -110,14 +215,27 @@ export async function ciServeObserve(opts, rt = newCiServeRuntime()) {
110
215
  }
111
216
  else if (state.target === undefined) {
112
217
  // What the branch held before this runner watched it was applied by whatever ran then.
113
- state.target = head;
114
- writeState(place.stateFile, state);
218
+ update((s) => {
219
+ s.target = head;
220
+ });
115
221
  events.push({ type: "baseline", commit: head });
116
222
  }
117
223
  else if (head !== state.target) {
224
+ const from = state.target;
225
+ const move = targetMove(place.top, place.branch, from, head);
118
226
  const entry = state.jobs[ciServeKey({ job: "apply", commit: head })];
119
- if (!entry)
120
- triggers.push({ job: "apply", commit: head, base: state.target });
227
+ if (move === "unknown") {
228
+ events.push({ type: "error", message: `${place.branch} is at ${head.slice(0, 12)} on the forge, and that commit could not be fetched; the next poll tries again` });
229
+ }
230
+ else if (move === "other" || (!entry && skipsCi(place.top, head))) {
231
+ // #4122: a move that is not a fast-forward, or a head that asks for no run, is seen and not applied.
232
+ update((s) => {
233
+ s.target = head;
234
+ });
235
+ events.push({ type: "not-run", job: "apply", commit: head, reason: move === "other" ? "not-fast-forward" : "skip-ci", ...(move === "other" ? { branch: place.branch, from } : {}) });
236
+ }
237
+ else if (!entry)
238
+ triggers.push({ job: "apply", commit: head, base: from });
121
239
  else if (opts.rerunFailed && failedEntry(entry, ciServeKey(entry), rt))
122
240
  triggers.push({ job: "apply", commit: head, base: entry.base, rerun: true });
123
241
  }
@@ -140,10 +258,21 @@ export async function ciServeObserve(opts, rt = newCiServeRuntime()) {
140
258
  const key = ciServeKey({ job: "plan", commit: pr.headSha, pr: pr.number });
141
259
  const entry = state.jobs[key];
142
260
  const base = pr.baseSha ?? head;
143
- if (!base)
261
+ if (!base || state.skipped?.[key])
144
262
  continue;
145
- if (!entry)
146
- triggers.push({ job: "plan", commit: pr.headSha, base, pr: pr.number });
263
+ if (!entry) {
264
+ // #4122: a head whose message asks for no run is seen and not planned.
265
+ if (gitStatus(place.top, ["cat-file", "-e", `${pr.headSha}^{commit}`]).code !== 0)
266
+ tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", `refs/pull/${pr.number}/head`]);
267
+ if (skipsCi(place.top, pr.headSha)) {
268
+ update((s) => {
269
+ s.skipped = { ...s.skipped, [key]: "skip-ci" };
270
+ });
271
+ events.push({ type: "not-run", job: "plan", commit: pr.headSha, pr: pr.number, reason: "skip-ci" });
272
+ }
273
+ else
274
+ triggers.push({ job: "plan", commit: pr.headSha, base, pr: pr.number });
275
+ }
147
276
  else if (opts.rerunFailed && failedEntry(entry, key, rt))
148
277
  triggers.push({ job: "plan", commit: pr.headSha, base: entry.base, pr: pr.number, rerun: true });
149
278
  }
@@ -199,6 +328,12 @@ export async function ciServeSchedule(opts, triggers, rt = newCiServeRuntime())
199
328
  }
200
329
  lease = got.lease;
201
330
  }
331
+ // #4121: with no other job of this serve writing it, the clone's ledger follows the forge's.
332
+ if (rt.inflight.size === 0) {
333
+ const ledger = reconcileCiServeLedger(place.top);
334
+ if (ledger)
335
+ emit(ledger);
336
+ }
202
337
  const previous = readCiServeState(place.stateFile, fresh).jobs[key];
203
338
  const attempt = (previous?.attempts ?? 0) + 1;
204
339
  const runId = `chant-serve-${now().getTime()}-${t.job}-${t.commit.slice(0, 8)}`;
@@ -238,10 +373,14 @@ async function runJob(opts, place, t, paths, held) {
238
373
  }, Math.max(1000, Math.floor(held.ttlMs / 3)))
239
374
  : undefined;
240
375
  renew?.unref?.();
376
+ let merged;
241
377
  try {
242
378
  // The pull request's head is on the forge's refs/pull/<n>/head, the target's on the branch.
243
379
  tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", t.job === "plan" ? `refs/pull/${t.pr}/head` : place.branch]);
244
380
  const hostEnv = { ...(opts.job?.hostEnv ?? process.env), GITHUB_REPOSITORY: opts.repo };
381
+ // An apply is for a merge: pass the pull request whose merge commit it is, so the commit status link and the run index use `<pr>/<commit>`. None found, or a forge that errs, is today's behaviour.
382
+ if (t.job === "apply" && opts.client.pullRequestFor)
383
+ merged = (await opts.client.pullRequestFor(t.commit).catch(() => null)) ?? undefined;
245
384
  const run = opts.run ?? ciRun;
246
385
  const r = await run({
247
386
  ...opts.job,
@@ -251,17 +390,17 @@ async function runJob(opts, place, t, paths, held) {
251
390
  env: opts.env,
252
391
  forge: opts.forge,
253
392
  base: t.base,
254
- ...(t.job === "plan" ? { pr: t.pr } : {}),
393
+ ...(t.job === "plan" ? { pr: t.pr } : merged !== undefined ? { pr: merged } : {}),
255
394
  commit: t.commit,
256
395
  output: paths.report,
257
396
  runId: paths.runId,
258
397
  ...(paths.resume ? { resumeRecord: paths.resume } : {}),
259
398
  });
260
399
  const why = r.record.failure ? `${r.record.failure.code}: ${r.record.failure.reason}` : r.failure ? `${r.failure.phase}: ${r.failure.error.split("\n")[0]}` : undefined;
261
- return { status: r.status === "ok" ? "ok" : "fail", ...(why && r.status !== "ok" ? { failure: why } : {}), ...(r.report ? { report: r.report } : {}) };
400
+ return { status: r.status === "ok" ? "ok" : "fail", ...(merged !== undefined ? { pr: merged } : {}), ...(why && r.status !== "ok" ? { failure: why } : {}), ...(r.report ? { report: r.report } : {}) };
262
401
  }
263
402
  catch (err) {
264
- return { status: "fail", failure: message(err) };
403
+ return { status: "fail", ...(merged !== undefined ? { pr: merged } : {}), failure: message(err) };
265
404
  }
266
405
  finally {
267
406
  if (renew)
@@ -319,14 +458,50 @@ export function formatCiServeEvent(e) {
319
458
  return `${at(e.job, e.commit, e.pr)} started, run ${e.runId}, attempt ${e.attempt}${e.resume ? ", resuming" : ""}`;
320
459
  case "settled":
321
460
  return `${at(e.job, e.commit, e.pr)} ${e.status === "ok" ? "passed" : `failed${e.failure ? `: ${e.failure}` : ""}`}${e.report ? `; report ${e.report}` : ""}`;
461
+ case "not-run":
462
+ return e.reason === "skip-ci"
463
+ ? `${at(e.job, e.commit, e.pr)} is not run: its commit message says [skip ci]`
464
+ : `${e.branch ?? "the target branch"} moved from ${e.from?.slice(0, 12) ?? "a commit"} to ${e.commit.slice(0, 12)}, which is not a fast-forward, so ${e.commit.slice(0, 12)} is recorded as seen and not applied`;
465
+ case "ledger":
466
+ return e.action === "deleted"
467
+ ? `the forge has no ${LEDGER}, so this clone's, at ${e.local.slice(0, 12)}, was deleted`
468
+ : `the forge's ${LEDGER} at ${e.forge.slice(0, 12)} does not contain this clone's at ${e.local.slice(0, 12)}, so this clone's was reset to the forge's and not pushed`;
322
469
  case "error":
323
470
  return `error: ${e.message}`;
324
471
  }
325
472
  }
326
473
  // ── The command ──────────────────────────────────────────────────────────────
327
- const USAGE = "chant ci serve --forge forgejo|github --repo <owner/repo> --env <env> [--interval <s>] [--max-jobs <n>] [--state <dir>] " +
328
- "[--author <login>]... [--once] [--rerun-failed] [--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] " +
329
- "[--image <image>] [--before-script <line>]... [--pr-build] [--pr-build-command <line>] [--chant <command>] [--pass-env <NAME>]... [--key <runner key.pem>]";
474
+ /**
475
+ * `chant ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]` (#4099):
476
+ * make the host's runner key when there is none, print its public half on
477
+ * stdout, and say whether the target's head lists it. With --trust, commit
478
+ * the runner entry on a branch of its own and push it for review.
479
+ */
480
+ export function runnerKeySetupCommand(ctx) {
481
+ const { args } = ctx;
482
+ const cwd = process.cwd();
483
+ const top = gitTop(cwd);
484
+ if (!top)
485
+ throw new CiRunError("the runner key is kept in the repository's git directory, and this directory is not in a git repository");
486
+ if (args.ciPrincipal !== undefined && !args.trustKey)
487
+ throw new CiRunError("--principal names who --trust lists the key for; give --trust with it");
488
+ const branch = args.branch ?? "main";
489
+ const setup = setupRunnerKey({
490
+ top,
491
+ stateDir: args.statePath ? resolve(args.statePath) : defaultCiServeStateDir(cwd),
492
+ branch,
493
+ ...(args.ciPrincipal ? { principal: args.ciPrincipal } : {}),
494
+ ...(args.trustKey ? { trust: true } : {}),
495
+ ...(args.by ? { by: args.by } : {}),
496
+ });
497
+ console.log(setup.key.publicKey);
498
+ console.error(formatRunnerKeySetup(setup, branch));
499
+ return 0;
500
+ }
501
+ const USAGE = "chant ci serve --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>] | " +
502
+ "chant ci serve --forge forgejo|github --repo <owner/repo> --env <env> [--interval <s>] [--max-jobs <n>] [--state <dir>] " +
503
+ "[--author <login>]... [--once] [--rerun-failed] [--gate <name>] [--wave-gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--no-status] [--status-url <url template>] " +
504
+ "[--image <image>] [--before-script <line>]... [--pr-build] [--pr-build-command <line>] [--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [--unsigned]";
330
505
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
331
506
  /** `--interval`: seconds, or a duration such as `30s` or `2m`. Default 30 seconds. */
332
507
  export function ciServeInterval(value) {
@@ -341,6 +516,8 @@ export function ciServeInterval(value) {
341
516
  export async function runCiServe(ctx) {
342
517
  const { args } = ctx;
343
518
  try {
519
+ if (args.setupKey)
520
+ return runnerKeySetupCommand(ctx);
344
521
  const forge = args.forge;
345
522
  if (forge !== "forgejo" && forge !== "github")
346
523
  throw new CiRunError(`--forge names the forge ci serve polls: --forge forgejo|github${forge ? `, not ${JSON.stringify(forge)}` : ""}`);
@@ -352,41 +529,69 @@ export async function runCiServe(ctx) {
352
529
  if (!ENV_NAME.test(name))
353
530
  throw new CiRunError(`--pass-env takes a variable's name, not ${JSON.stringify(name)}`);
354
531
  const intervalMs = ciServeInterval(args.interval);
355
- const fromFlag = args.key !== undefined;
356
- const runnerKey = fromFlag ? readFileSync(resolve(args.key), "utf-8") : process.env[RUNNER_KEY_ENV] || undefined;
532
+ const cwd = process.cwd();
533
+ const top = gitTop(cwd);
534
+ if (!top)
535
+ throw new CiRunError("ci serve runs in a clone of the repository it serves, and this directory is not in a git repository");
536
+ const stateDir = args.statePath ? resolve(args.statePath) : defaultCiServeStateDir(cwd);
537
+ const branch = args.branch ?? "main";
538
+ // #4099: the host's key, checked against .chant/trust.json at the target's head before the first poll.
539
+ // #4156: signing is opt-in. Without ci.green.signedSince no key is quiet, and an untrusted one warns once and serves unsigned.
540
+ const keyed = ciServeRunnerKey({
541
+ top,
542
+ stateDir,
543
+ branch,
544
+ ...(args.key !== undefined ? { keyFile: resolve(args.key) } : {}),
545
+ envKey: process.env[RUNNER_KEY_ENV] || undefined,
546
+ unsigned: args.unsigned,
547
+ required: signedRunsRequired(cwd),
548
+ });
549
+ const fromFlag = keyed.fromFlag;
550
+ const runnerKey = keyed.pem;
551
+ if (keyed.warning)
552
+ console.error(formatWarning({ message: keyed.warning }));
553
+ if (runnerKey !== undefined)
554
+ console.error(`ci serve: signing runs with the runner key from ${keyed.source}`);
555
+ else if (keyed.note && args.verbose)
556
+ console.error(`ci serve: ${keyed.note}`);
557
+ // #4096: read once, so a loose or broken secrets file stops the serve before its first poll.
558
+ const secrets = ciSecretsOf(args.ciSecrets);
559
+ // #4160: without --report-url, the declaration's ci.reportUrl.
560
+ const reportUrl = await reportUrlOf(args.reportUrl, cwd);
561
+ if (reportUrl && !args.reportUrl)
562
+ console.error(`ci serve: reports and the run index go to ${reportUrl}, the declaration's ci.reportUrl`);
357
563
  const hostEnv = { ...process.env, GITHUB_REPOSITORY: args.ciRepo };
358
564
  const client = forgeFromEnv(forge, hostEnv);
565
+ const { status, warning } = ciStatusFromEnv({ forge, hostEnv, noStatus: args.noStatus, statusUrl: args.statusUrl });
566
+ if (warning)
567
+ console.error(formatWarning({ message: warning }));
359
568
  if (!client.openPullRequests || !client.branchHead)
360
569
  throw new CiRunError(`the ${forge} client cannot list pull requests`);
361
570
  const opts = {
362
- cwd: process.cwd(),
571
+ cwd,
363
572
  forge,
364
573
  repo: args.ciRepo,
365
574
  env: args.env,
366
- client: { openPullRequests: client.openPullRequests.bind(client), branchHead: client.branchHead.bind(client) },
367
- ...(args.statePath ? { stateDir: isAbsolute(args.statePath) ? args.statePath : resolve(args.statePath) } : {}),
575
+ client: { openPullRequests: client.openPullRequests.bind(client), branchHead: client.branchHead.bind(client), pullRequestFor: client.pullRequestFor.bind(client) },
576
+ stateDir,
368
577
  ...(args.maxJobs ? { maxJobs: args.maxJobs } : {}),
369
578
  ...(args.ciAuthors?.length ? { authors: args.ciAuthors } : {}),
370
579
  ...(args.rerunFailed ? { rerunFailed: true } : {}),
371
580
  job: {
372
- loop: {
373
- ...(args.gate ? { gate: args.gate } : {}),
374
- ...(args.branch ? { branch: args.branch } : {}),
375
- ...(args.eventsJson ? { events: true } : {}),
376
- ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
377
- ...(args.prBuild ? { build: true } : {}),
378
- ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
379
- },
581
+ loop: ciLoopOptions(reportUrl ? { ...args, reportUrl } : args),
582
+ ...(args.runIndexLimit ? { runIndexLimit: args.runIndexLimit } : {}),
380
583
  ...(args.image ? { image: args.image } : {}),
381
584
  ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
382
585
  ...(args.ciChant ? { chant: args.ciChant } : {}),
383
586
  ...(args.passEnv?.length ? { passEnv: args.passEnv } : {}),
587
+ ...(status ? { status } : {}),
588
+ ...(secrets ? { secrets } : {}),
384
589
  ...(runnerKey !== undefined ? { runnerKey, runnerKeyFromEnv: !fromFlag } : {}),
385
590
  ...(args.eventsJson ? { runEvents: (line) => void process.stdout.write(line) } : {}),
386
591
  hostEnv,
387
592
  stepOutput: (line) => void process.stderr.write(`${line}\n`),
388
593
  },
389
- log: (e) => console.error(e.type === "error" ? formatWarning({ message: formatCiServeEvent(e) }) : `ci serve: ${formatCiServeEvent(e)}`),
594
+ log: (e) => console.error(e.type === "error" || e.type === "ledger" ? formatWarning({ message: formatCiServeEvent(e) }) : `ci serve: ${formatCiServeEvent(e)}`),
390
595
  };
391
596
  if (args.once) {
392
597
  const pass = await ciServePass(opts);
@@ -171,6 +171,14 @@ export type CiSource = "checks" | "runs";
171
171
  /** The declaration's `ci` block (#3573). */
172
172
  export interface CiDeclaration {
173
173
  green: CiGreen | null;
174
+ /**
175
+ * `ci.reportUrl` (#4160): where the pull-request jobs copy their reports
176
+ * and `chant ci run` keeps the run index, `<reportUrl>/runs/index.json`.
177
+ * An `s3://` or `file://` URL, as `--report-url` takes, without a trailing
178
+ * slash. The commands that take `--report-url` default to it. Null when
179
+ * the declaration sets none.
180
+ */
181
+ reportUrl: string | null;
174
182
  }
175
183
  export interface MemberRole {
176
184
  name: string;