@intentius/chant 0.124.31 → 0.124.33

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 (509) hide show
  1. package/bin/chant +7 -2
  2. package/dist/ci-jobs.d.ts +4 -0
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +9 -2
  5. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/commands/check-lexicon.js +18 -12
  10. package/dist/cli/commands/import.d.ts +10 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/import.js +37 -0
  13. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  15. package/dist/cli/commands/onboard.d.ts.map +1 -1
  16. package/dist/cli/commands/onboard.js +8 -4
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/commands/update.js +2 -1
  19. package/dist/cli/dependencies.d.ts +18 -0
  20. package/dist/cli/dependencies.d.ts.map +1 -0
  21. package/dist/cli/dependencies.js +120 -0
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.js +38 -4
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/fan-out.js +12 -4
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +16 -6
  28. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  29. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  30. package/dist/cli/handlers/lifecycle-health.js +150 -0
  31. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  32. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  33. package/dist/cli/handlers/lifecycle.js +1 -1
  34. package/dist/cli/handlers/lint.d.ts.map +1 -1
  35. package/dist/cli/handlers/lint.js +6 -0
  36. package/dist/cli/handlers/operator.d.ts +2 -2
  37. package/dist/cli/handlers/operator.js +3 -3
  38. package/dist/cli/handlers/pr.d.ts +16 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -1
  40. package/dist/cli/handlers/pr.js +610 -32
  41. package/dist/cli/handlers/promote.d.ts.map +1 -1
  42. package/dist/cli/handlers/promote.js +19 -2
  43. package/dist/cli/handlers/release.d.ts +16 -1
  44. package/dist/cli/handlers/release.d.ts.map +1 -1
  45. package/dist/cli/handlers/release.js +83 -11
  46. package/dist/cli/handlers/run.d.ts +13 -1
  47. package/dist/cli/handlers/run.d.ts.map +1 -1
  48. package/dist/cli/handlers/run.js +38 -15
  49. package/dist/cli/main.d.ts.map +1 -1
  50. package/dist/cli/main.js +121 -33
  51. package/dist/cli/parse-args.d.ts.map +1 -1
  52. package/dist/cli/parse-args.js +37 -0
  53. package/dist/cli/registry.d.ts +29 -6
  54. package/dist/cli/registry.d.ts.map +1 -1
  55. package/dist/cli/registry.js +6 -2
  56. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  57. package/dist/codegen/lexicon-regen.d.ts +4 -0
  58. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  59. package/dist/codegen/lexicon-regen.js +1 -1
  60. package/dist/codegen/pinned-upgrade.js +2 -2
  61. package/dist/components/capability.d.ts +44 -3
  62. package/dist/components/capability.d.ts.map +1 -1
  63. package/dist/components/capability.js +21 -0
  64. package/dist/components/changed-files.d.ts +7 -1
  65. package/dist/components/changed-files.d.ts.map +1 -1
  66. package/dist/components/changed-files.js +9 -1
  67. package/dist/components/deploy-inputs.d.ts +91 -0
  68. package/dist/components/deploy-inputs.d.ts.map +1 -0
  69. package/dist/components/deploy-inputs.js +184 -0
  70. package/dist/components/driver.d.ts +38 -0
  71. package/dist/components/driver.d.ts.map +1 -1
  72. package/dist/components/driver.js +56 -6
  73. package/dist/components/fan-out-record.d.ts +6 -1
  74. package/dist/components/fan-out-record.d.ts.map +1 -1
  75. package/dist/components/fan-out-record.js +24 -0
  76. package/dist/components/fan-out-run.d.ts +20 -0
  77. package/dist/components/fan-out-run.d.ts.map +1 -1
  78. package/dist/components/fan-out-run.js +21 -1
  79. package/dist/components/gate-plan.d.ts +8 -0
  80. package/dist/components/gate-plan.d.ts.map +1 -1
  81. package/dist/components/gate-plan.js +6 -0
  82. package/dist/components/pr-pipeline.d.ts +98 -12
  83. package/dist/components/pr-pipeline.d.ts.map +1 -1
  84. package/dist/components/pr-pipeline.js +146 -23
  85. package/dist/components/pr-run.d.ts +128 -30
  86. package/dist/components/pr-run.d.ts.map +1 -1
  87. package/dist/components/pr-run.js +290 -70
  88. package/dist/components/release-compile.d.ts +37 -2
  89. package/dist/components/release-compile.d.ts.map +1 -1
  90. package/dist/components/release-compile.js +64 -8
  91. package/dist/components/release-promote.d.ts +17 -3
  92. package/dist/components/release-promote.d.ts.map +1 -1
  93. package/dist/components/release-promote.js +79 -18
  94. package/dist/components/verbs/bom-writer.d.ts +1 -1
  95. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  96. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  97. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  98. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  99. package/dist/components/wave-plan.d.ts +15 -2
  100. package/dist/components/wave-plan.d.ts.map +1 -1
  101. package/dist/components/wave-plan.js +44 -5
  102. package/dist/discovery/fold-import.d.ts.map +1 -1
  103. package/dist/discovery/fold-import.js +16 -6
  104. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  105. package/dist/discovery/sandbox/bundle.js +6 -0
  106. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  107. package/dist/discovery/specifier-resolve.js +8 -2
  108. package/dist/fold/fold.d.ts.map +1 -1
  109. package/dist/fold/fold.js +68 -1
  110. package/dist/fold/subset.d.ts +37 -0
  111. package/dist/fold/subset.d.ts.map +1 -1
  112. package/dist/fold/subset.js +88 -0
  113. package/dist/graph-detail.d.ts.map +1 -1
  114. package/dist/graph-detail.js +32 -7
  115. package/dist/health.d.ts +110 -0
  116. package/dist/health.d.ts.map +1 -0
  117. package/dist/health.js +105 -0
  118. package/dist/health.schema.json +191 -0
  119. package/dist/lexicon-module.d.ts +10 -0
  120. package/dist/lexicon-module.d.ts.map +1 -1
  121. package/dist/lexicon-module.js +53 -8
  122. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  123. package/dist/lexicon-plugin-helpers.js +6 -4
  124. package/dist/lexicon.d.ts +29 -0
  125. package/dist/lexicon.d.ts.map +1 -1
  126. package/dist/lifecycle/affected.d.ts.map +1 -1
  127. package/dist/lifecycle/affected.js +7 -1
  128. package/dist/lifecycle/merge-record.d.ts +104 -0
  129. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  130. package/dist/lifecycle/merge-record.js +102 -0
  131. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  132. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  133. package/dist/lifecycle/publish-attachments.js +27 -13
  134. package/dist/lifecycle/publish-npm.d.ts +3 -2
  135. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  136. package/dist/lifecycle/publish-npm.js +4 -3
  137. package/dist/lifecycle/release-at.d.ts +40 -0
  138. package/dist/lifecycle/release-at.d.ts.map +1 -0
  139. package/dist/lifecycle/release-at.js +83 -0
  140. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  141. package/dist/lifecycle/release-cut.js +23 -12
  142. package/dist/lifecycle/release-ledger.d.ts +21 -0
  143. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  144. package/dist/lifecycle/release-ledger.js +24 -0
  145. package/dist/lifecycle/release-publish.d.ts +28 -12
  146. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  147. package/dist/lifecycle/release-publish.js +29 -17
  148. package/dist/lifecycle/release-store.d.ts +25 -2
  149. package/dist/lifecycle/release-store.d.ts.map +1 -1
  150. package/dist/lifecycle/release-store.js +40 -13
  151. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/run-ledger.js +1 -0
  153. package/dist/lifecycle/work-dir.d.ts +45 -0
  154. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  155. package/dist/lifecycle/work-dir.js +78 -0
  156. package/dist/lint/discover.d.ts +17 -9
  157. package/dist/lint/discover.d.ts.map +1 -1
  158. package/dist/lint/discover.js +62 -54
  159. package/dist/lint/rules/op/index.d.ts +2 -1
  160. package/dist/lint/rules/op/index.d.ts.map +1 -1
  161. package/dist/lint/rules/op/index.js +4 -2
  162. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  163. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  164. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  165. package/dist/module-source.d.ts +57 -0
  166. package/dist/module-source.d.ts.map +1 -0
  167. package/dist/module-source.js +104 -0
  168. package/dist/op/activities/activity-contracts.d.ts +5 -0
  169. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  170. package/dist/op/activities/activity-contracts.js +3 -0
  171. package/dist/op/activities/factory.js +1 -1
  172. package/dist/op/activities/shell.d.ts +38 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -1
  174. package/dist/op/activities/shell.js +89 -3
  175. package/dist/op/builders.d.ts +7 -0
  176. package/dist/op/builders.d.ts.map +1 -1
  177. package/dist/op/builders.js +7 -0
  178. package/dist/op/discover.d.ts.map +1 -1
  179. package/dist/op/discover.js +13 -3
  180. package/dist/op/gate.d.ts.map +1 -1
  181. package/dist/op/gate.js +3 -0
  182. package/dist/op/op-waves-run.d.ts.map +1 -1
  183. package/dist/op/op-waves-run.js +20 -5
  184. package/dist/op/operator.d.ts +2 -2
  185. package/dist/op/operator.js +2 -2
  186. package/dist/op/runtime.d.ts +2 -0
  187. package/dist/op/runtime.d.ts.map +1 -1
  188. package/dist/op/steward.d.ts +6 -4
  189. package/dist/op/steward.d.ts.map +1 -1
  190. package/dist/op/steward.js +5 -3
  191. package/dist/op/work-lease-run.d.ts +20 -7
  192. package/dist/op/work-lease-run.d.ts.map +1 -1
  193. package/dist/op/work-lease-run.js +56 -12
  194. package/dist/pr-forge.d.ts +10 -0
  195. package/dist/pr-forge.d.ts.map +1 -1
  196. package/dist/pr-forge.js +39 -0
  197. package/dist/pr-loop.d.ts +155 -7
  198. package/dist/pr-loop.d.ts.map +1 -1
  199. package/dist/pr-loop.js +137 -11
  200. package/dist/pr-report-url.d.ts +4 -4
  201. package/dist/pr-report-url.d.ts.map +1 -1
  202. package/dist/pr-report-url.js +5 -5
  203. package/dist/runner-start.d.ts +179 -0
  204. package/dist/runner-start.d.ts.map +1 -0
  205. package/dist/runner-start.js +277 -0
  206. package/dist/ts-loader.d.ts +38 -4
  207. package/dist/ts-loader.d.ts.map +1 -1
  208. package/dist/ts-loader.js +190 -16
  209. package/dist/wave-runtime.mjs +238 -231
  210. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  211. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  212. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  213. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  214. package/dist/workspace/chant-migrations.js +2 -0
  215. package/dist/workspace/check.schema.json +70 -2
  216. package/dist/workspace/checks/boxes.d.ts +6 -6
  217. package/dist/workspace/checks/boxes.js +9 -9
  218. package/dist/workspace/checks/security.d.ts +35 -0
  219. package/dist/workspace/checks/security.d.ts.map +1 -0
  220. package/dist/workspace/checks/security.js +305 -0
  221. package/dist/workspace/checks.d.ts +2 -0
  222. package/dist/workspace/checks.d.ts.map +1 -1
  223. package/dist/workspace/checks.js +5 -0
  224. package/dist/workspace/ci-builds.d.ts +163 -0
  225. package/dist/workspace/ci-builds.d.ts.map +1 -0
  226. package/dist/workspace/ci-builds.js +315 -0
  227. package/dist/workspace/ci-green-cli.d.ts +2 -1
  228. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  229. package/dist/workspace/ci-green-cli.js +25 -5
  230. package/dist/workspace/ci-green-forge.d.ts +18 -4
  231. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  232. package/dist/workspace/ci-green-forge.js +12 -0
  233. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  234. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  235. package/dist/workspace/ci-green-workflow.js +128 -1
  236. package/dist/workspace/ci-green.d.ts +117 -16
  237. package/dist/workspace/ci-green.d.ts.map +1 -1
  238. package/dist/workspace/ci-green.js +358 -43
  239. package/dist/workspace/ci-last-green.schema.json +49 -0
  240. package/dist/workspace/ci-list.schema.json +47 -7
  241. package/dist/workspace/ci-report-url.d.ts +18 -0
  242. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  243. package/dist/workspace/ci-report-url.js +33 -0
  244. package/dist/workspace/ci-run-index.d.ts +13 -1
  245. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  246. package/dist/workspace/ci-run-index.js +6 -4
  247. package/dist/workspace/ci-run-index.schema.json +6 -4
  248. package/dist/workspace/ci-run.d.ts +85 -6
  249. package/dist/workspace/ci-run.d.ts.map +1 -1
  250. package/dist/workspace/ci-run.js +262 -64
  251. package/dist/workspace/ci-runner-key.d.ts +16 -3
  252. package/dist/workspace/ci-runner-key.d.ts.map +1 -1
  253. package/dist/workspace/ci-runner-key.js +30 -10
  254. package/dist/workspace/ci-runner-start.d.ts +77 -0
  255. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  256. package/dist/workspace/ci-runner-start.js +185 -0
  257. package/dist/workspace/ci-serve.d.ts +130 -11
  258. package/dist/workspace/ci-serve.d.ts.map +1 -1
  259. package/dist/workspace/ci-serve.js +323 -40
  260. package/dist/workspace/declaration.d.ts +25 -0
  261. package/dist/workspace/declaration.d.ts.map +1 -1
  262. package/dist/workspace/declaration.js +14 -2
  263. package/dist/workspace/declaration.schema.json +30 -2
  264. package/dist/workspace/export.d.ts +1 -1
  265. package/dist/workspace/export.d.ts.map +1 -1
  266. package/dist/workspace/export.js +7 -3
  267. package/dist/workspace/ls.d.ts +8 -1
  268. package/dist/workspace/ls.d.ts.map +1 -1
  269. package/dist/workspace/ls.js +2 -1
  270. package/dist/workspace/ls.schema.json +9 -2
  271. package/dist/workspace/member-pipeline.d.ts +1 -0
  272. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  273. package/dist/workspace/member-pipeline.js +2 -0
  274. package/dist/workspace/merge-record.schema.json +49 -0
  275. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  276. package/dist/workspace/pin-integrity.js +3 -2
  277. package/dist/workspace/pr-report.schema.json +75 -8
  278. package/dist/workspace/reason-codes.d.ts +18 -1
  279. package/dist/workspace/reason-codes.d.ts.map +1 -1
  280. package/dist/workspace/reason-codes.js +20 -2
  281. package/dist/workspace/release-candidates.d.ts +129 -0
  282. package/dist/workspace/release-candidates.d.ts.map +1 -0
  283. package/dist/workspace/release-candidates.js +233 -0
  284. package/dist/workspace/release-candidates.schema.json +353 -0
  285. package/dist/workspace/run-event.schema.json +3 -1
  286. package/dist/workspace/security.d.ts +40 -0
  287. package/dist/workspace/security.d.ts.map +1 -0
  288. package/dist/workspace/security.js +99 -0
  289. package/dist/workspace/status.schema.json +3 -1
  290. package/dist/workspace/write-scope.d.ts +9 -0
  291. package/dist/workspace/write-scope.d.ts.map +1 -1
  292. package/dist/workspace/write-scope.js +11 -1
  293. package/package.json +8 -1
  294. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  295. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  296. package/src/ci-jobs.ts +11 -2
  297. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  298. package/src/cli/commands/audit.test.ts +2 -2
  299. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  300. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  301. package/src/cli/commands/check-lexicon.test.ts +1 -1
  302. package/src/cli/commands/check-lexicon.ts +16 -11
  303. package/src/cli/commands/import.ts +40 -0
  304. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  305. package/src/cli/commands/init-lexicon.test.ts +8 -5
  306. package/src/cli/commands/onboard.test.ts +45 -0
  307. package/src/cli/commands/onboard.ts +8 -4
  308. package/src/cli/commands/update.test.ts +6 -0
  309. package/src/cli/commands/update.ts +4 -1
  310. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  311. package/src/cli/dependencies.e2e.test.ts +101 -0
  312. package/src/cli/dependencies.test.ts +93 -0
  313. package/src/cli/dependencies.ts +122 -0
  314. package/src/cli/handlers/build.test.ts +105 -0
  315. package/src/cli/handlers/build.ts +35 -4
  316. package/src/cli/handlers/fan-out.test.ts +73 -0
  317. package/src/cli/handlers/fan-out.ts +11 -4
  318. package/src/cli/handlers/graph.test.ts +76 -0
  319. package/src/cli/handlers/graph.ts +19 -12
  320. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  321. package/src/cli/handlers/lifecycle-health.ts +157 -0
  322. package/src/cli/handlers/lifecycle.ts +1 -1
  323. package/src/cli/handlers/lint.test.ts +42 -0
  324. package/src/cli/handlers/lint.ts +6 -0
  325. package/src/cli/handlers/operator.ts +3 -3
  326. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  327. package/src/cli/handlers/pr-record.test.ts +201 -0
  328. package/src/cli/handlers/pr-waves.test.ts +449 -0
  329. package/src/cli/handlers/pr.test.ts +156 -0
  330. package/src/cli/handlers/pr.ts +648 -33
  331. package/src/cli/handlers/promote.test.ts +43 -1
  332. package/src/cli/handlers/promote.ts +19 -3
  333. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  334. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  335. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  336. package/src/cli/handlers/release.e2e.test.ts +19 -28
  337. package/src/cli/handlers/release.ts +81 -11
  338. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  339. package/src/cli/handlers/run-signer.test.ts +79 -0
  340. package/src/cli/handlers/run.ts +34 -16
  341. package/src/cli/main.test.ts +18 -0
  342. package/src/cli/main.ts +120 -33
  343. package/src/cli/parse-args.ts +28 -0
  344. package/src/cli/registry.ts +35 -8
  345. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  346. package/src/codegen/lexicon-regen.ts +1 -1
  347. package/src/codegen/pinned-upgrade.ts +2 -2
  348. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  349. package/src/components/capability.ts +51 -3
  350. package/src/components/changed-files.test.ts +16 -1
  351. package/src/components/changed-files.ts +9 -1
  352. package/src/components/deploy-inputs.test.ts +108 -0
  353. package/src/components/deploy-inputs.ts +225 -0
  354. package/src/components/driver.test.ts +36 -1
  355. package/src/components/driver.ts +87 -4
  356. package/src/components/fan-out-record.ts +29 -1
  357. package/src/components/fan-out-run.test.ts +35 -0
  358. package/src/components/fan-out-run.ts +40 -1
  359. package/src/components/gate-plan.ts +9 -0
  360. package/src/components/pr-pipeline.test.ts +100 -0
  361. package/src/components/pr-pipeline.ts +184 -28
  362. package/src/components/pr-run.test.ts +323 -18
  363. package/src/components/pr-run.ts +355 -76
  364. package/src/components/release-compile.test.ts +34 -0
  365. package/src/components/release-compile.ts +89 -8
  366. package/src/components/release-promote.test.ts +55 -2
  367. package/src/components/release-promote.ts +93 -23
  368. package/src/components/verbs/bom-writer.ts +1 -1
  369. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  370. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  371. package/src/components/wave-plan.ts +48 -8
  372. package/src/discovery/fold-host-method.test.ts +87 -0
  373. package/src/discovery/fold-import.ts +15 -5
  374. package/src/discovery/fold-package-composite.test.ts +27 -0
  375. package/src/discovery/fold-specifiers.test.ts +8 -0
  376. package/src/discovery/sandbox/bundle.ts +6 -0
  377. package/src/discovery/specifier-resolve.ts +8 -2
  378. package/src/fold/fold.test.ts +72 -0
  379. package/src/fold/fold.ts +71 -0
  380. package/src/fold/subset.test.ts +31 -0
  381. package/src/fold/subset.ts +99 -0
  382. package/src/graph-detail.test.ts +57 -0
  383. package/src/graph-detail.ts +33 -7
  384. package/src/health.schema.json +191 -0
  385. package/src/health.test.ts +62 -0
  386. package/src/health.ts +178 -0
  387. package/src/lexicon-dist.e2e.test.ts +254 -0
  388. package/src/lexicon-module.test.ts +47 -0
  389. package/src/lexicon-module.ts +49 -7
  390. package/src/lexicon-plugin-helpers.ts +6 -4
  391. package/src/lexicon.ts +43 -1
  392. package/src/lifecycle/affected.test.ts +7 -0
  393. package/src/lifecycle/affected.ts +6 -1
  394. package/src/lifecycle/merge-record.test.ts +111 -0
  395. package/src/lifecycle/merge-record.ts +154 -0
  396. package/src/lifecycle/publish-attachments.ts +25 -13
  397. package/src/lifecycle/publish-npm.ts +4 -3
  398. package/src/lifecycle/publish-targets.test.ts +34 -4
  399. package/src/lifecycle/release-at.test.ts +48 -0
  400. package/src/lifecycle/release-at.ts +100 -0
  401. package/src/lifecycle/release-cut.test.ts +41 -0
  402. package/src/lifecycle/release-cut.ts +22 -11
  403. package/src/lifecycle/release-ledger.ts +34 -0
  404. package/src/lifecycle/release-publish.test.ts +43 -13
  405. package/src/lifecycle/release-publish.ts +38 -18
  406. package/src/lifecycle/release-store.ts +57 -13
  407. package/src/lifecycle/run-ledger.test.ts +13 -0
  408. package/src/lifecycle/run-ledger.ts +1 -0
  409. package/src/lifecycle/work-dir.test.ts +77 -0
  410. package/src/lifecycle/work-dir.ts +82 -0
  411. package/src/lint/discover.test.ts +46 -2
  412. package/src/lint/discover.ts +55 -49
  413. package/src/lint/rules/op/index.ts +4 -2
  414. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  415. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  416. package/src/module-source.test.ts +85 -0
  417. package/src/module-source.ts +112 -0
  418. package/src/op/activities/activity-contracts.ts +3 -0
  419. package/src/op/activities/factory.ts +1 -1
  420. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  421. package/src/op/activities/shell.test.ts +44 -1
  422. package/src/op/activities/shell.ts +110 -3
  423. package/src/op/builders.ts +7 -0
  424. package/src/op/discover-root.test.ts +31 -2
  425. package/src/op/discover.ts +14 -4
  426. package/src/op/gate.test.ts +30 -0
  427. package/src/op/gate.ts +3 -0
  428. package/src/op/op-waves-run.ts +20 -5
  429. package/src/op/op-waves.test.ts +49 -0
  430. package/src/op/operator.ts +2 -2
  431. package/src/op/runtime.ts +2 -0
  432. package/src/op/step-console.test.ts +4 -2
  433. package/src/op/steward.ts +6 -4
  434. package/src/op/work-lease-run.test.ts +88 -2
  435. package/src/op/work-lease-run.ts +52 -12
  436. package/src/pr-forge-bundle.test.ts +2 -2
  437. package/src/pr-forge.test.ts +42 -0
  438. package/src/pr-forge.ts +40 -0
  439. package/src/pr-loop-bundle.test.ts +2 -1
  440. package/src/pr-loop.test.ts +146 -1
  441. package/src/pr-loop.ts +278 -17
  442. package/src/pr-report-url.ts +7 -7
  443. package/src/runner-start-bundle.test.ts +29 -0
  444. package/src/runner-start.test.ts +179 -0
  445. package/src/runner-start.ts +329 -0
  446. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  447. package/src/ts-loader.test.ts +89 -1
  448. package/src/ts-loader.ts +180 -13
  449. package/src/workspace/changes.test.ts +1 -1
  450. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  451. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  452. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  453. package/src/workspace/chant-migrations.ts +2 -0
  454. package/src/workspace/check-contract.test.ts +3 -2
  455. package/src/workspace/check.schema.json +70 -2
  456. package/src/workspace/checks/boxes.ts +9 -9
  457. package/src/workspace/checks/security.test.ts +138 -0
  458. package/src/workspace/checks/security.ts +305 -0
  459. package/src/workspace/checks.ts +5 -0
  460. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  461. package/src/workspace/ci-builds.test.ts +174 -0
  462. package/src/workspace/ci-builds.ts +385 -0
  463. package/src/workspace/ci-green-builds.test.ts +180 -0
  464. package/src/workspace/ci-green-cli.test.ts +54 -0
  465. package/src/workspace/ci-green-cli.ts +26 -5
  466. package/src/workspace/ci-green-forge.ts +30 -4
  467. package/src/workspace/ci-green-runs.test.ts +53 -11
  468. package/src/workspace/ci-green-workflow.test.ts +84 -1
  469. package/src/workspace/ci-green-workflow.ts +139 -2
  470. package/src/workspace/ci-green.test.ts +6 -3
  471. package/src/workspace/ci-green.ts +408 -48
  472. package/src/workspace/ci-last-green.schema.json +49 -0
  473. package/src/workspace/ci-list.schema.json +47 -7
  474. package/src/workspace/ci-report-url.test.ts +220 -0
  475. package/src/workspace/ci-report-url.ts +33 -0
  476. package/src/workspace/ci-run-index.schema.json +6 -4
  477. package/src/workspace/ci-run-index.test.ts +42 -3
  478. package/src/workspace/ci-run-index.ts +19 -5
  479. package/src/workspace/ci-run.test.ts +203 -8
  480. package/src/workspace/ci-run.ts +295 -63
  481. package/src/workspace/ci-runner-key.test.ts +55 -17
  482. package/src/workspace/ci-runner-key.ts +36 -11
  483. package/src/workspace/ci-runner-start.test.ts +151 -0
  484. package/src/workspace/ci-runner-start.ts +193 -0
  485. package/src/workspace/ci-serve-builds.test.ts +86 -0
  486. package/src/workspace/ci-serve.test.ts +209 -6
  487. package/src/workspace/ci-serve.ts +341 -42
  488. package/src/workspace/declaration.schema.json +30 -2
  489. package/src/workspace/declaration.test.ts +40 -0
  490. package/src/workspace/declaration.ts +40 -3
  491. package/src/workspace/export.ts +6 -3
  492. package/src/workspace/ls.schema.json +9 -2
  493. package/src/workspace/ls.ts +10 -4
  494. package/src/workspace/member-pipeline.ts +2 -1
  495. package/src/workspace/merge-record.schema.json +49 -0
  496. package/src/workspace/pin-integrity.test.ts +5 -0
  497. package/src/workspace/pin-integrity.ts +3 -2
  498. package/src/workspace/pr-report.schema.json +75 -8
  499. package/src/workspace/reason-codes.test.ts +2 -0
  500. package/src/workspace/reason-codes.ts +20 -2
  501. package/src/workspace/release-candidates.schema.json +353 -0
  502. package/src/workspace/release-candidates.test.ts +106 -0
  503. package/src/workspace/release-candidates.ts +315 -0
  504. package/src/workspace/run-event.schema.json +3 -1
  505. package/src/workspace/security.test.ts +70 -0
  506. package/src/workspace/security.ts +118 -0
  507. package/src/workspace/status.schema.json +3 -1
  508. package/src/workspace/write-scope.test.ts +29 -1
  509. package/src/workspace/write-scope.ts +12 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `chant ci run plan|apply` (#4094, epic #4101): run one pull-request job as
2
+ * `chant ci run plan|record|apply` (#4094, epic #4101, record #4168): run one pull-request job as
3
3
  * a pipeline Op, the first slice of the chant runner.
4
4
  *
5
5
  * The job is the typed value `prLoopJobs` (../components/pr-pipeline.ts)
@@ -44,6 +44,30 @@
44
44
  * With a forge client (#4097) the job also sets a commit status at its
45
45
  * commit: `pending` when it starts, `success` or `failure` when it settles.
46
46
  * A status the forge refuses is a warning and never fails the job.
47
+ *
48
+ * Signing is opt-in (#4156). With no runner key the record is unsigned, and
49
+ * a reader counts it with no signer. A key `.chant/trust.json` does not list
50
+ * leaves it unsigned with a note `--verbose` prints, unless the declaration
51
+ * sets `ci.green.signedSince`; then a run left unsigned is a warning.
52
+ *
53
+ * In place (`--in-place`, #4156) the job runs in the checkout it is started
54
+ * in, as a generated forge job does: no worktree, no setup phase and no
55
+ * commit status of its own (the forge shows the job). The steps get the
56
+ * job's whole environment less `CHANT_RUNNER_KEY`, since the forge job is
57
+ * already the boundary, and the record's `runOrigin` names the forge's run,
58
+ * job and attempt (`resolveCiJob`).
59
+ *
60
+ * Local (`--local`, #4161) runs the job the same way on this machine and
61
+ * leaves nothing outside it. It implies `--no-push` and `--no-status`, and
62
+ * writes nothing under a report URL, given or declared: the command gets no
63
+ * `--report-url`, and gets `--no-status`, so pr-plan and pr-apply post no
64
+ * status or note either. The record is still written to the local
65
+ * `chant/lifecycle`, where a reader on this machine finds it beside CI's for
66
+ * the same commit. It says `runOrigin: { forge: "local" }` and its events say
67
+ * `runtime: "local"`. `ci tick --source runs` and `ci list` never count it.
68
+ * A pr-apply still pushes its own release records and gate facts, as the
69
+ * command always does, since a local apply changes the same infrastructure
70
+ * a CI one does.
47
71
  */
48
72
  import { execFileSync } from "node:child_process";
49
73
  import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
@@ -53,25 +77,38 @@ import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prLoopJobs, prLoopSetup, PR_LOOP_I
53
77
  import { detectForge, ForgeEnvironmentError, forgeFromEnv, FORGE_KINDS } from "../pr-forge.js";
54
78
  import { PR_APPLY_GATE } from "../pr-loop.js";
55
79
  import { formatError, formatWarning } from "../cli/format.js";
56
- import { pushLifecycleReport } from "../lifecycle/git.js";
80
+ import { resolveCiJob } from "../lifecycle/release-ledger.js";
81
+ import { fetchLifecycleStatus, pushLifecycleReport } from "../lifecycle/git.js";
57
82
  import { ACTIVITY_PROFILES } from "../op/activity-profiles.js";
58
83
  import { shellCmd } from "../op/activities/shell.js";
59
84
  import { OpRunFailure, runOpLocally } from "../op/local-executor.js";
60
85
  import { redactText } from "../op/redact.js";
61
86
  import { findWorkspaceRoot } from "../project-root.js";
62
87
  import { parseReportUrl, reportKey } from "../pr-report-url.js";
63
- import { ciTagSigner, RUNNER_KEY_ENV } from "./ci-green.js";
88
+ import { reportUrlOf } from "./ci-report-url.js";
89
+ import { ciTagSigner, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green.js";
90
+ import { loadRunnerKey } from "./trust/dsse.js";
64
91
  import { ciRedactor, ciSecretsFor, CI_SECRETS_ENV, readCiSecrets } from "./ci-secrets.js";
65
92
  import { updateCiRunIndex } from "./ci-run-index.js";
66
93
  import { resolveMemberContext } from "./member-pipeline.js";
67
94
  import { signOpRunRecord } from "./trust/op-run-statement.js";
68
95
  import { gitTop } from "./tree.js";
69
96
  import { hostRunnerKey } from "./ci-runner-key.js";
70
- export const CI_JOB_NAMES = ["plan", "apply"];
97
+ import { requireRunnerStart } from "./ci-runner-start.js";
98
+ import { securityGroupsAt } from "./security.js";
99
+ export const CI_JOB_NAMES = ["plan", "apply", "record"];
71
100
  /** The Op each job is recorded as, which `ci.green` phases match. */
72
- export const CI_JOB_OPS = { plan: "pr-plan", apply: "pr-apply" };
101
+ export const CI_JOB_OPS = { plan: "pr-plan", apply: "pr-apply", record: "pr-record" };
102
+ /** What each job's Op says it does. */
103
+ const CI_JOB_OVERVIEWS = {
104
+ plan: "Plan the members this pull request reaches",
105
+ apply: "Apply the plan a reviewer approved",
106
+ record: "Record the plan on the merged commit; nothing applies",
107
+ };
73
108
  /** Where a job the chant runner ran says it ran (#4094). */
74
109
  export const CHANT_RUNNER_ORIGIN = { forge: "chant" };
110
+ /** Where a job `chant ci run --local` ran says it ran (#4161): this machine, outside CI. */
111
+ export const CHANT_LOCAL_ORIGIN = { forge: "local" };
75
112
  /** The runner's variables every job gets, when the runner has them. */
76
113
  export const CI_RUN_BASE_ENV = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TZ"];
77
114
  /**
@@ -178,7 +215,7 @@ export function ciJobOp(input) {
178
215
  const op = CI_JOB_OPS[input.job.jobName];
179
216
  return {
180
217
  name: op,
181
- overview: input.job.jobName === "plan" ? "Plan the members this pull request reaches" : "Apply the plan a reviewer approved",
218
+ overview: CI_JOB_OVERVIEWS[input.job.jobName],
182
219
  labels: { Env: input.env, Pipeline: "pr", Job: input.job.jobName },
183
220
  phases,
184
221
  };
@@ -190,6 +227,17 @@ function exitCodeOf(error) {
190
227
  const m = /command exited (\d+)/.exec(error ?? "");
191
228
  return m ? Number(m[1]) : undefined;
192
229
  }
230
+ /**
231
+ * The code `chant ci run` exits with for a settled job: 0 when it passed,
232
+ * else its command's own code when that is 1 to 255, such as 3 for a moved
233
+ * plan or a waiting wave (#4156), else 1. The run index records the same
234
+ * code (#4165).
235
+ */
236
+ export function ciJobExitCode(result) {
237
+ if (result.status === "ok")
238
+ return 0;
239
+ return result.exitCode && result.exitCode > 0 && result.exitCode < 256 ? result.exitCode : 1;
240
+ }
193
241
  /** The first line of a step's error, for a reason. */
194
242
  const firstLine = (text) => text.split("\n").find((l) => l.trim() !== "")?.trim() ?? text;
195
243
  /**
@@ -249,12 +297,12 @@ export function ciJobFailure(input) {
249
297
  const STATUS_DESCRIPTION = 140;
250
298
  const cut = (text, max = STATUS_DESCRIPTION) => (text.length > max ? `${text.slice(0, max - 3)}...` : text);
251
299
  /**
252
- * A job's status context: `chant/pr-plan` for the plan, and for the apply
300
+ * A job's status context: `chant/pr-plan` for the plan, `chant/pr-record` for the record (#4168), and for the apply
253
301
  * `chant/<gate>`, which is `chant/pr-apply` unless `--gate` renamed the gate.
254
302
  * pr-plan's own status is `chant/plan` (../pr-loop.ts), a different context.
255
303
  */
256
304
  export function ciStatusContext(job, gate) {
257
- return job === "apply" ? `chant/${gate ?? PR_APPLY_GATE}` : `chant/${CI_JOB_OPS.plan}`;
305
+ return job === "apply" ? `chant/${gate ?? PR_APPLY_GATE}` : `chant/${CI_JOB_OPS[job]}`;
258
306
  }
259
307
  /**
260
308
  * The link a job's status carries: the `--status-url` template with `{key}`
@@ -396,7 +444,9 @@ async function recordInIndex(input) {
396
444
  ended: input.record.ended,
397
445
  runId: clean(input.record.id),
398
446
  key: clean(reportKey(pr, input.commit, input.member)),
399
- runOrigin: CHANT_RUNNER_ORIGIN,
447
+ runOrigin: input.runOrigin,
448
+ signer: input.signer,
449
+ exitCode: input.exitCode,
400
450
  };
401
451
  await updateCiRunIndex({ target, entry, ...(input.limit ? { limit: input.limit } : {}), warn: input.warn });
402
452
  }
@@ -413,12 +463,25 @@ export async function ciRun(req) {
413
463
  if (!top)
414
464
  throw new CiRunError("a job runs in a fresh checkout of a commit, and this directory is not in a git repository");
415
465
  const commit = resolveJobCommit(top, req.commit ?? "HEAD");
466
+ if (req.inPlace && commit !== resolveJobCommit(top, "HEAD"))
467
+ throw new CiRunError(`--in-place runs the checkout's HEAD, and --commit names ${commit.slice(0, 12)}`);
468
+ if (req.local && req.inPlace)
469
+ throw new CiRunError("--in-place runs a forge job in the forge's checkout, and --local runs one on this machine; pass one of them");
416
470
  const place = ciJobPlace(cwd, top);
417
- const jobs = prLoopJobs(req.forge, req.env, req.loop ?? {}, place.member);
418
- const job = jobs.find((j) => j.jobName === req.job);
419
- if (!job)
471
+ const jobs = prLoopJobs(req.forge, req.env, req.local ? localLoop(req.loop) : (req.loop ?? {}), place.member);
472
+ const found = jobs.find((j) => j.jobName === req.job);
473
+ if (!found)
420
474
  throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
421
- const jobEnv = ciJobEnv(job, {
475
+ if (req.wave !== undefined && (req.job !== "apply" || !req.loop?.waveGate))
476
+ throw new CiRunError("--wave names the one wave an apply runs, and takes --wave-gate: ci run apply --wave-gate <gate> --wave <k>");
477
+ // #4147: before anything runs or any secret reaches a step, the provisioner's word that this runner was started for this job.
478
+ // #4168: under the isolated-runners security group an apply job requires the statement too, unless it runs on this machine.
479
+ const isolated = !req.requireRunnerStart && !req.local && req.job === "apply" && securityGroupsAt(cwd).has("isolated-runners");
480
+ const started = req.requireRunnerStart || isolated ? runnerStartFor(req, { top, commit, hostEnv }) : undefined;
481
+ // A generated wave job's command, as prLoopWaveJobs writes it. #4161: a local job's command posts no status or note.
482
+ const command = `${found.command}${req.wave !== undefined ? ` --wave ${req.wave}` : ""}${req.local ? " --no-status" : ""}`;
483
+ const job = { ...found, command, ...(req.wave !== undefined ? { wave: req.wave } : {}) };
484
+ const own = ciJobEnv(job, {
422
485
  forge: req.forge,
423
486
  env: req.env,
424
487
  values: { base: req.base, pr: req.pr, commit },
@@ -426,11 +489,16 @@ export async function ciRun(req) {
426
489
  passEnv: req.passEnv,
427
490
  secrets: ciSecretsFor(req.secrets, req.job),
428
491
  });
492
+ // #4156: in place, the forge job's environment is the job's, less the runner key.
493
+ const jobEnv = req.inPlace ? { ...definedEnv(hostEnv), ...own } : own;
494
+ delete jobEnv[RUNNER_KEY_ENV];
429
495
  // #4096: the secrets and the job's secret-looking variables, redacted from everything the run writes and returns.
430
- const redact = ciRedactor(jobEnv, req.secrets);
496
+ // In place the forge's own variables come too; one whose value is short, such as a flag, is left out, so it can't blank common words.
497
+ const redact = ciRedactor(req.inPlace ? { ...longValues(jobEnv), ...own } : jobEnv, req.secrets);
431
498
  try {
432
- const result = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
433
- return redact ? { ...result, warnings: result.warnings.map(redact), ...(result.pushWarning ? { pushWarning: redact(result.pushWarning) } : {}) } : result;
499
+ const ran = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
500
+ const result = started ? { ...ran, notes: [started, ...ran.notes] } : ran;
501
+ return redact ? { ...result, warnings: result.warnings.map(redact), notes: result.notes.map(redact), ...(result.pushWarning ? { pushWarning: redact(result.pushWarning) } : {}) } : result;
434
502
  }
435
503
  catch (err) {
436
504
  if (redact && err instanceof Error)
@@ -438,23 +506,89 @@ export async function ciRun(req) {
438
506
  throw err;
439
507
  }
440
508
  }
509
+ /**
510
+ * `--require-runner-start` (#4147): the runner-start statement for this
511
+ * forge job, checked against `.chant/trust.json` at `--base` (the commit
512
+ * before the change, as a push's `before` is), or at the job's commit
513
+ * without one. Throws {@link CiRunError} unless it verifies.
514
+ */
515
+ function runnerStartFor(req, at) {
516
+ if (req.local)
517
+ throw new CiRunError("--require-runner-start checks a forge job's runner, and --local runs the job on this machine");
518
+ const base = req.base ? resolveJobCommit(at.top, req.base) : at.commit;
519
+ try {
520
+ return requireRunnerStart({ top: at.top, base, commit: at.commit, env: at.hostEnv }).reason;
521
+ }
522
+ catch (err) {
523
+ throw new CiRunError(message(err));
524
+ }
525
+ }
526
+ /** The generator's options for a `--local` job (#4161): the same, with no report URL. */
527
+ function localLoop(loop) {
528
+ const { reportUrl: _dropped, ...rest } = loop ?? {};
529
+ return rest;
530
+ }
531
+ /** The variables of `env` whose value is long enough to be a credential, 8 characters or more. */
532
+ function longValues(env) {
533
+ return Object.fromEntries(Object.entries(env).filter(([, value]) => value.length >= 8));
534
+ }
535
+ /** The variables of `env` that have a value. */
536
+ function definedEnv(env) {
537
+ const out = {};
538
+ for (const [name, value] of Object.entries(env))
539
+ if (value !== undefined)
540
+ out[name] = value;
541
+ return out;
542
+ }
543
+ /**
544
+ * Who signs the run (#4156): the runner key, checked against
545
+ * `.chant/trust.json` at the commit. A key that isn't an Ed25519 PEM is an
546
+ * error, and so is a `--key` the file does not list. A key from the
547
+ * environment that it does not list, and no key at all, leave the run
548
+ * unsigned: a warning when the declaration's `ci.green.signedSince` asks
549
+ * for signed runs, a note for `--verbose` otherwise.
550
+ */
551
+ function ciRunSigner(req, at) {
552
+ // #4161: a local run never counts, signed or not, so nothing about its signature warns.
553
+ const required = req.local ? null : signedRunsRequired(at.cwd);
554
+ const say = (text) => (required ? { warnings: [`${text}, and ci.green.signedSince is ${required}, so it will not count`], notes: [] } : { warnings: [], notes: [text] });
555
+ if (req.runnerKey === undefined)
556
+ return say("there is no runner key, so the run's record is unsigned");
557
+ try {
558
+ loadRunnerKey(req.runnerKey);
559
+ }
560
+ catch (err) {
561
+ throw new CiRunError(`the runner key${req.runnerKeyFromEnv ? ` in ${RUNNER_KEY_ENV}` : ""} is not an Ed25519 private key in PEM: ${message(err)}`);
562
+ }
563
+ try {
564
+ return { signer: ciTagSigner(at.top, at.commit, req.runnerKey, "the runs it signed"), warnings: [], notes: [] };
565
+ }
566
+ catch (err) {
567
+ if (!req.runnerKeyFromEnv)
568
+ throw new CiRunError(`the run would not be signed: ${message(err)}`);
569
+ return say(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
570
+ }
571
+ }
572
+ /** Where the job runs: a fresh worktree of the commit, or with `inPlace` the checkout itself, which is left as it is. */
573
+ function jobCheckout(top, commit, inPlace) {
574
+ return inPlace ? { dir: top, remove() { } } : freshCheckout(top, commit);
575
+ }
441
576
  /** {@link ciRun} once the job and its environment are known. */
442
577
  async function runCiJob(req, at) {
443
578
  const { cwd, top, commit, place, job, jobEnv, redact } = at;
444
- // The record is signed against .chant/trust.json at the commit the job runs, so a key that commit does not list is refused before anything runs.
445
- let signer;
446
- const early = [];
447
- if (req.runnerKey !== undefined) {
448
- try {
449
- signer = ciTagSigner(top, commit, req.runnerKey, "the runs it signed");
450
- }
451
- catch (err) {
452
- if (!req.runnerKeyFromEnv)
453
- throw new CiRunError(`the run would not be signed: ${message(err)}`);
454
- early.push(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
455
- }
456
- }
457
- const checkout = freshCheckout(top, commit);
579
+ const local = req.local === true;
580
+ const status = local ? undefined : req.status;
581
+ const reportUrl = local ? undefined : req.loop?.reportUrl;
582
+ // The record is signed against .chant/trust.json at the commit the job runs, so a --key that commit does not list is refused before anything runs.
583
+ const signing = ciRunSigner(req, { cwd, top, commit });
584
+ const signer = signing.signer;
585
+ const early = signing.warnings;
586
+ const notes = signing.notes;
587
+ const origin = local ? CHANT_LOCAL_ORIGIN : req.inPlace ? (resolveCiJob(req.hostEnv ?? process.env)?.runOrigin ?? CHANT_RUNNER_ORIGIN) : CHANT_RUNNER_ORIGIN;
588
+ // In place, the job writes its record on the checkout's chant/lifecycle, which a forge checkout may not have fetched.
589
+ if (req.inPlace && req.push !== false)
590
+ await fetchLifecycleStatus({ cwd: top }).catch(() => undefined);
591
+ const checkout = jobCheckout(top, commit, req.inPlace);
458
592
  try {
459
593
  const jobDir = place.dir === "." ? checkout.dir : join(checkout.dir, place.dir);
460
594
  const op = ciJobOp({
@@ -462,7 +596,7 @@ async function runCiJob(req, at) {
462
596
  env: req.env,
463
597
  cwd: jobDir,
464
598
  jobEnv,
465
- setup: prLoopSetup(req.image ?? PR_LOOP_IMAGE),
599
+ setup: req.inPlace ? [] : prLoopSetup(req.image ?? PR_LOOP_IMAGE),
466
600
  build: [...(req.beforeScript ?? []), ...(job.build ?? [])],
467
601
  ...(req.chant ? { chant: req.chant } : {}),
468
602
  });
@@ -474,14 +608,14 @@ async function runCiJob(req, at) {
474
608
  copyFileSync(resume.host, resume.job);
475
609
  }
476
610
  const statusContext = ciStatusContext(req.job, req.loop?.gate);
477
- const statusUrl = req.status
478
- ? ciStatusUrl({ ...(req.status.urlTemplate ? { urlTemplate: req.status.urlTemplate } : {}), ...(req.loop?.reportUrl ? { reportUrl: req.loop.reportUrl } : {}), ...(req.pr !== undefined ? { pr: req.pr } : {}), commit, ...(place.member ? { member: place.member } : {}) })
611
+ const statusUrl = status
612
+ ? ciStatusUrl({ ...(status.urlTemplate ? { urlTemplate: status.urlTemplate } : {}), ...(reportUrl ? { reportUrl } : {}), ...(req.pr !== undefined ? { pr: req.pr } : {}), commit, ...(place.member ? { member: place.member } : {}) })
479
613
  : undefined;
480
614
  const postStatus = async (state, description) => {
481
- if (!req.status)
615
+ if (!status)
482
616
  return;
483
617
  try {
484
- await req.status.client.setCommitStatus(commit, { context: statusContext, state, description: cut(redact ? redact(description) : description), ...(statusUrl ? { targetUrl: statusUrl } : {}) });
618
+ await status.client.setCommitStatus(commit, { context: statusContext, state, description: cut(redact ? redact(description) : description), ...(statusUrl ? { targetUrl: statusUrl } : {}) });
485
619
  }
486
620
  catch (err) {
487
621
  warnings.push(`could not set the ${statusContext} status: ${message(err)}`);
@@ -498,7 +632,7 @@ async function runCiJob(req, at) {
498
632
  cwd: jobDir,
499
633
  commit,
500
634
  ledger: { cwd: jobDir },
501
- runner: { kind: "pipeline", runtime: "ci", runOrigin: CHANT_RUNNER_ORIGIN },
635
+ runner: { kind: "pipeline", runtime: local ? "local" : "ci", runOrigin: origin },
502
636
  describeFailure: (records) => ciJobFailure({ job: req.job, op, records, reportDir: join(jobDir, PR_LOOP_REPORT_DIR) }),
503
637
  ...(req.runEvents ? { runEvents: { write: req.runEvents } } : {}),
504
638
  ...(req.stepOutput ? { stepOutput: req.stepOutput } : {}),
@@ -533,28 +667,40 @@ async function runCiJob(req, at) {
533
667
  let report = null;
534
668
  if (existsSync(written)) {
535
669
  report = resolve(cwd, req.output ?? PR_LOOP_REPORT_DIR);
536
- cpSync(written, report, { recursive: true });
537
- if (redact)
538
- redactTree(report, redact);
670
+ // In place the report is already where the forge keeps it, and stays as the command wrote it.
671
+ if (!(existsSync(report) && realpathSync(report) === realpathSync(written))) {
672
+ cpSync(written, report, { recursive: true });
673
+ if (redact)
674
+ redactTree(report, redact);
675
+ }
539
676
  }
540
- if (req.loop?.reportUrl) {
541
- await recordInIndex({ url: req.loop.reportUrl, job: req.job, commit, member: place.member, pr: req.pr, record: result.record, reportDir: written, limit: req.runIndexLimit, ...(redact ? { redact } : {}), warn: (w) => warnings.push(w) });
677
+ const failed = result.records.find((r) => r.status === "fail");
678
+ const exitCode = failed?.phase === req.job ? exitCodeOf(failed.error) : undefined;
679
+ if (reportUrl) {
680
+ await recordInIndex({ url: reportUrl, job: req.job, commit, member: place.member, pr: req.pr, record: result.record, runOrigin: origin, signer: result.record.statement && signer ? signer.principal : null, exitCode: ciJobExitCode({ status: result.status, ...(exitCode !== undefined ? { exitCode } : {}) }), reportDir: written, limit: req.runIndexLimit, ...(redact ? { redact } : {}), warn: (w) => warnings.push(w) });
542
681
  }
543
- if (ledgerError)
682
+ // In place the forge job's outcome is the command's, so a record that could not be written is a warning there.
683
+ if (ledgerError && req.inPlace)
684
+ warnings.push(ledgerError);
685
+ else if (ledgerError)
544
686
  throw new CiRunError(ledgerError);
545
- const pushed = req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
546
- const failed = result.records.find((r) => r.status === "fail");
687
+ const pushed = local
688
+ ? { pushed: false }
689
+ : req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
547
690
  return {
548
691
  op: op.name,
549
692
  status: result.status,
550
693
  record: result.record,
551
694
  ...(failed ? { failure: { phase: failed.phase, error: failed.error ?? "the step failed" } } : {}),
695
+ ...(exitCode !== undefined ? { exitCode } : {}),
552
696
  report,
553
- signer: signer?.principal ?? null,
697
+ signer: result.record.statement && signer ? signer.principal : null,
554
698
  pushed: pushed.pushed,
555
699
  ...(pushed.pushWarning ? { pushWarning: pushed.pushWarning } : {}),
556
700
  warnings,
701
+ notes,
557
702
  checkout: checkout.dir,
703
+ ...(local ? { local: true } : {}),
558
704
  };
559
705
  }
560
706
  finally {
@@ -592,18 +738,39 @@ export function formatCiRun(r) {
592
738
  lines.push(` failure: ${why.code}${why.phase ? ` in ${why.phase}` : ""}${why.exitCode !== undefined ? `, exit ${why.exitCode}` : ""}: ${why.reason}`);
593
739
  else if (r.failure)
594
740
  lines.push(` ${r.failure.phase}: ${r.failure.error}`);
595
- lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned, so ci tick from runs does not count it"}`);
741
+ lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned"}`);
596
742
  if (r.report)
597
743
  lines.push(` report: ${r.report}`);
598
- if (!r.pushed)
744
+ if (r.local)
745
+ lines.push(" local run: the record stays on this machine, with no push, commit status or report upload");
746
+ else if (!r.pushed)
599
747
  lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
600
748
  return lines.join("\n");
601
749
  }
602
750
  // ── The command ──────────────────────────────────────────────────────────────
603
- const USAGE = "chant ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
604
- "[--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--run-index-limit <n>] [--image <image>] [--before-script <line>]... " +
605
- "[--pr-build] [--pr-build-command <line>] [--no-status] [--status-url <url template>] " +
606
- "[--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [-o <dir>] | chant ci run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]";
751
+ /**
752
+ * The generator's options from `ci run`'s and `ci serve`'s flags, as `chant
753
+ * build --components --generate <lexicon> --pr-loop` takes them: `--gate`,
754
+ * `--branch`, `--events-json`, `--report-url`, `--pr-build`,
755
+ * `--pr-build-command`, `--wave-gate` (#4003) and `--require-review`
756
+ * (#4168; `--no-require-review`, the default, is still accepted).
757
+ */
758
+ export function ciLoopOptions(args) {
759
+ return {
760
+ ...(args.gate ? { gate: args.gate } : {}),
761
+ ...(args.branch ? { branch: args.branch } : {}),
762
+ ...(args.eventsJson ? { events: true } : {}),
763
+ ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
764
+ ...(args.prBuild ? { build: true } : {}),
765
+ ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
766
+ ...(args.waveGate ? { waveGate: args.waveGate } : {}),
767
+ ...(args.requireReview === true ? { requireReview: true } : {}),
768
+ };
769
+ }
770
+ const USAGE = "chant ci run plan|record|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
771
+ "[--gate <name>] [--wave-gate <name>] [--wave <k>] [--branch <name>] [--events-json] [--report-url <url>] [--run-index-limit <n>] [--image <image>] [--before-script <line>]... " +
772
+ "[--pr-build] [--pr-build-command <line>] [--require-review] [--no-status] [--status-url <url template>] " +
773
+ "[--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [-o <dir>] [--in-place | --local] [--no-push] [--verbose] | chant ci run --at <sha> | chant ci run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]";
607
774
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
608
775
  /** The host's runner key in `--state`, or in ci serve's default state directory, when `--setup-key` made one there (#4099). */
609
776
  function hostRunnerKeyAt(cwd, statePath) {
@@ -623,15 +790,40 @@ export function ciSecretsOf(flag, env = process.env) {
623
790
  const file = flag ?? (env[CI_SECRETS_ENV] || undefined);
624
791
  return file === undefined ? undefined : readCiSecrets(resolve(file));
625
792
  }
626
- /** `chant ci run plan|apply` (#4094). */
793
+ /**
794
+ * `chant ci run --at <sha>` (#4169): ask for every required phase to be built
795
+ * at a commit of the green branch. On GitHub, with a token in the
796
+ * environment, the workflows holding the phases start at once.
797
+ */
798
+ async function requestBuildCommand(at) {
799
+ const { requestBuild } = await import("./ci-green.js");
800
+ const { ciForgeFromEnv } = await import("./ci-green-forge.js");
801
+ let forge;
802
+ try {
803
+ forge = process.env.GITHUB_REPOSITORY ? ciForgeFromEnv("github") : undefined;
804
+ }
805
+ catch {
806
+ forge = undefined;
807
+ }
808
+ const r = await requestBuild({ cwd: process.cwd(), at, ...(forge ? { forge } : {}) });
809
+ for (const w of r.warnings)
810
+ console.error(formatWarning({ message: w }));
811
+ const what = `${r.request.phases.join(", ")} at ${r.request.commit.slice(0, 12)}`;
812
+ console.log(`${r.existed ? `${r.request.tag} already asks for a build` : `Asked for ${what}: ${r.request.tag}`}. ` +
813
+ (r.dispatched.length > 0 ? `Started ${r.dispatched.join(", ")}.` : "The next ci tick starts it on GitHub, and ci serve --build-command serves it for chant runs."));
814
+ return 0;
815
+ }
816
+ /** `chant ci run plan|record|apply` (#4094, #4168). */
627
817
  export async function runCiRun(ctx) {
628
818
  const { args } = ctx;
629
819
  try {
630
820
  if (args.setupKey)
631
821
  return (await import("./ci-serve.js")).runnerKeySetupCommand(ctx);
822
+ if (args.at !== undefined && args.extraPositional === undefined)
823
+ return await requestBuildCommand(args.at);
632
824
  const job = args.extraPositional;
633
- if (job !== "plan" && job !== "apply")
634
- throw new CiRunError(`ci run takes the job to run, plan or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
825
+ if (job !== "plan" && job !== "apply" && job !== "record")
826
+ throw new CiRunError(`ci run takes the job to run, plan, record or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
635
827
  if (!args.env)
636
828
  throw new CiRunError("--env names the environment the pipeline is for, as chant build --components --generate <lexicon> --pr-loop took it: --env <env>");
637
829
  const forge = args.forge ?? detectForge(process.env);
@@ -647,10 +839,14 @@ export async function runCiRun(ctx) {
647
839
  const hostKey = fromFlag || process.env[RUNNER_KEY_ENV] ? undefined : hostRunnerKeyAt(process.cwd(), args.statePath);
648
840
  const runnerKey = fromFlag ? readFileSync(resolve(args.key), "utf-8") : process.env[RUNNER_KEY_ENV] || hostKey?.pem;
649
841
  const hostEnv = process.env;
650
- const { status, warning } = ciStatusFromEnv({ forge: forge, hostEnv, noStatus: args.noStatus, statusUrl: args.statusUrl });
842
+ // #4156: in place the forge shows the job, so ci run sets no status of its own.
843
+ // #4161: a local run sets no status, so it builds no client and warns about none.
844
+ const { status, warning } = ciStatusFromEnv({ forge: forge, hostEnv, noStatus: args.noStatus || args.ciInPlace || args.local, statusUrl: args.statusUrl });
651
845
  if (warning)
652
846
  console.error(formatWarning({ message: warning }));
653
847
  const secrets = ciSecretsOf(args.ciSecrets);
848
+ // #4160: without --report-url, the declaration's ci.reportUrl.
849
+ const reportUrl = await reportUrlOf(args.reportUrl, process.cwd());
654
850
  const result = await ciRun({
655
851
  cwd: process.cwd(),
656
852
  ...(status ? { status } : {}),
@@ -660,14 +856,12 @@ export async function runCiRun(ctx) {
660
856
  ...(args.base ? { base: args.base } : {}),
661
857
  ...(args.pr !== undefined ? { pr: args.pr } : {}),
662
858
  ...(args.commit ? { commit: args.commit } : {}),
663
- loop: {
664
- ...(args.gate ? { gate: args.gate } : {}),
665
- ...(args.branch ? { branch: args.branch } : {}),
666
- ...(args.eventsJson ? { events: true } : {}),
667
- ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
668
- ...(args.prBuild ? { build: true } : {}),
669
- ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
670
- },
859
+ loop: ciLoopOptions(reportUrl ? { ...args, reportUrl } : args),
860
+ ...(args.ciInPlace ? { inPlace: true } : {}),
861
+ ...(args.local ? { local: true } : {}),
862
+ ...(args.noPush ? { push: false } : {}),
863
+ ...(args.wave !== undefined ? { wave: args.wave } : {}),
864
+ ...(args.requireRunnerStart ? { requireRunnerStart: true } : {}),
671
865
  ...(args.runIndexLimit ? { runIndexLimit: args.runIndexLimit } : {}),
672
866
  ...(args.image ? { image: args.image } : {}),
673
867
  ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
@@ -681,8 +875,12 @@ export async function runCiRun(ctx) {
681
875
  });
682
876
  for (const w of result.warnings)
683
877
  console.error(formatWarning({ message: w }));
878
+ if (args.verbose)
879
+ for (const n of result.notes)
880
+ console.error(`ci run: ${n}`);
684
881
  console.error(formatCiRun(result));
685
- return result.status === "ok" ? 0 : 1;
882
+ // #4156: a failed command's own exit code, such as 3 for a moved plan, is the job's, as it was before the job ran through ci run.
883
+ return ciJobExitCode(result);
686
884
  }
687
885
  catch (err) {
688
886
  console.error(formatError({ message: `chant ci run: ${message(err)}`, hint: USAGE }));
@@ -101,9 +101,18 @@ export declare function formatRunnerKeySetup(s: RunnerKeySetup, branch: string):
101
101
  /**
102
102
  * The key `ci serve` signs with, checked before its first poll (#4099): the
103
103
  * `--key` file, else `CHANT_RUNNER_KEY`, else the host's key in the state
104
- * directory. Throws, with the command that lists it, when there is none or
105
- * `.chant/trust.json` at the target's head does not list it with the runner
106
- * role. With `unsigned` there is no key and nothing is checked.
104
+ * directory. Signing is opt-in (#4156):
105
+ *
106
+ * - With no key, `ci serve` serves unsigned and says so only in `note`.
107
+ * - A key `.chant/trust.json` at the target's head does not list with the
108
+ * runner role, or a target branch it can't find to check against, leaves
109
+ * it serving unsigned, with one `warning`.
110
+ * - With `required` (the declaration's `ci.green.signedSince`) each of
111
+ * those refuses to start, as before #4156.
112
+ *
113
+ * A key that is not an Ed25519 private key in PEM, or a `--key` file that
114
+ * can't be read, refuses either way. `unsigned` (`--unsigned`) skips the
115
+ * key without `required` and is ignored with it.
107
116
  */
108
117
  export declare function ciServeRunnerKey(req: {
109
118
  top: string;
@@ -112,9 +121,13 @@ export declare function ciServeRunnerKey(req: {
112
121
  keyFile?: string;
113
122
  envKey?: string;
114
123
  unsigned?: boolean;
124
+ /** The declaration's `ci.green.signedSince`, when it sets one: every run must then be signed. */
125
+ required?: string | null;
115
126
  }): {
116
127
  pem?: string;
117
128
  fromFlag: boolean;
118
129
  source: string;
130
+ warning?: string;
131
+ note?: string;
119
132
  };
120
133
  //# sourceMappingURL=ci-runner-key.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ci-runner-key.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-runner-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH,oDAAoD;AACpD,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,2FAA2F;AAC3F,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,OAAO,CAAC;CAClB;AA4BD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAGzE;AAED,yHAAyH;AACzH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAWnE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOtH;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,4HAA4H;AAC5H,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMzI;AAED,MAAM,WAAW,mBAAmB;IAClC,mGAAmG;IACnG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mDAAmD;IACnD,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,mBAAmB,CAmChK;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,CAAC;IACnB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC;AAED,iIAAiI;AACjI,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAQvJ;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAe9E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwBnM"}
1
+ {"version":3,"file":"ci-runner-key.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-runner-key.ts"],"names":[],"mappings":"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"}
@@ -184,18 +184,31 @@ export function formatRunnerKeySetup(s, branch) {
184
184
  /**
185
185
  * The key `ci serve` signs with, checked before its first poll (#4099): the
186
186
  * `--key` file, else `CHANT_RUNNER_KEY`, else the host's key in the state
187
- * directory. Throws, with the command that lists it, when there is none or
188
- * `.chant/trust.json` at the target's head does not list it with the runner
189
- * role. With `unsigned` there is no key and nothing is checked.
187
+ * directory. Signing is opt-in (#4156):
188
+ *
189
+ * - With no key, `ci serve` serves unsigned and says so only in `note`.
190
+ * - A key `.chant/trust.json` at the target's head does not list with the
191
+ * runner role, or a target branch it can't find to check against, leaves
192
+ * it serving unsigned, with one `warning`.
193
+ * - With `required` (the declaration's `ci.green.signedSince`) each of
194
+ * those refuses to start, as before #4156.
195
+ *
196
+ * A key that is not an Ed25519 private key in PEM, or a `--key` file that
197
+ * can't be read, refuses either way. `unsigned` (`--unsigned`) skips the
198
+ * key without `required` and is ignored with it.
190
199
  */
191
200
  export function ciServeRunnerKey(req) {
192
- if (req.unsigned)
193
- return { fromFlag: false, source: "none (--unsigned)" };
201
+ const required = req.required ?? null;
202
+ const optIn = `ci.green.signedSince is ${required}, so every run must be signed`;
203
+ if (req.unsigned && !required)
204
+ return { fromFlag: false, source: "none (--unsigned)", note: "runs are not signed (--unsigned)" };
194
205
  const host = req.keyFile === undefined && !req.envKey ? hostRunnerKey(req.stateDir) : undefined;
195
206
  const pem = req.keyFile !== undefined ? readFileSync(req.keyFile, "utf-8") : req.envKey || host?.pem;
196
207
  const source = req.keyFile !== undefined ? req.keyFile : req.envKey ? "CHANT_RUNNER_KEY" : (host?.path ?? join(req.stateDir, RUNNER_KEY_FILE));
197
208
  if (pem === undefined) {
198
- throw new CiRunError(`there is no runner key: no --key, no CHANT_RUNNER_KEY and no ${source}. Make one with chant ci serve --setup-key, list it with --trust, or start with --unsigned`);
209
+ if (!required)
210
+ return { fromFlag: false, source: "none", note: "runs are not signed: there is no runner key. To verify who ran a job, add one with chant ci serve --setup-key --trust" };
211
+ throw new CiRunError(`${optIn}, and there is no runner key: no --key, no CHANT_RUNNER_KEY and no ${source}. Make one with chant ci serve --setup-key and list it with --trust`);
199
212
  }
200
213
  let publicKey;
201
214
  try {
@@ -205,12 +218,19 @@ export function ciServeRunnerKey(req) {
205
218
  throw new CiRunError(`the runner key from ${source} is not an Ed25519 private key in PEM: ${message(err)}`);
206
219
  }
207
220
  const head = targetHead(req.top, req.branch);
208
- if (!head)
209
- throw new CiRunError(`there is no branch ${req.branch} here or on origin, so the runner key can't be checked against its .chant/trust.json; fetch it, or start with --unsigned`);
221
+ if (!head) {
222
+ const why = `there is no branch ${req.branch} here or on origin, so the runner key can't be checked against its .chant/trust.json`;
223
+ if (required)
224
+ throw new CiRunError(`${optIn}, and ${why}; fetch it`);
225
+ return { fromFlag: false, source, warning: `${why}, so runs are not signed; fetch it to sign them` };
226
+ }
210
227
  const listed = runnerKeyListed(req.top, head.commit, pem);
211
228
  if (!listed.ok) {
212
- throw new CiRunError(`the runner key from ${source} is not trusted at ${branchAt(req.branch, head.commit)}: ${listed.reason}\n` +
213
- ` List it on a branch for review with chant ci serve --setup-key --trust, or run ${trustCommand(defaultRunnerPrincipal(), publicKey)} in a checkout of ${req.branch} and merge the change. Start with --unsigned to serve without signing.`);
229
+ const why = `the runner key from ${source} is not trusted at ${branchAt(req.branch, head.commit)}: ${listed.reason}`;
230
+ const fix = `List it on a branch for review with chant ci serve --setup-key --trust, or run ${trustCommand(defaultRunnerPrincipal(), publicKey)} in a checkout of ${req.branch} and merge the change.`;
231
+ if (required)
232
+ throw new CiRunError(`${optIn}, and ${why}\n ${fix}`);
233
+ return { fromFlag: false, source, warning: `${why}. Runs are not signed until it is. ${fix}` };
214
234
  }
215
235
  return { pem, fromFlag: req.keyFile !== undefined, source };
216
236
  }