@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
 
49
73
  import { execFileSync } from "node:child_process";
@@ -55,8 +79,8 @@ import { detectForge, ForgeEnvironmentError, forgeFromEnv, FORGE_KINDS, type Com
55
79
  import { PR_APPLY_GATE } from "../pr-loop";
56
80
  import type { CommandContext } from "../cli/registry";
57
81
  import { formatError, formatWarning } from "../cli/format";
58
- import type { RunOrigin } from "../lifecycle/release-ledger";
59
- import { pushLifecycleReport } from "../lifecycle/git";
82
+ import { resolveCiJob, type RunOrigin } from "../lifecycle/release-ledger";
83
+ import { fetchLifecycleStatus, pushLifecycleReport } from "../lifecycle/git";
60
84
  import { ACTIVITY_PROFILES } from "../op/activity-profiles";
61
85
  import type { ActivityFn, ActivityProfile } from "../op/activity-registry";
62
86
  import { shellCmd } from "../op/activities/shell";
@@ -66,24 +90,38 @@ import type { ActivityStep, OpConfig, PhaseDefinition } from "../op/types";
66
90
  import { redactText, type Redact } from "../op/redact";
67
91
  import { findWorkspaceRoot } from "../project-root";
68
92
  import { parseReportUrl, reportKey } from "../pr-report-url";
69
- import { ciTagSigner, RUNNER_KEY_ENV } from "./ci-green";
93
+ import { reportUrlOf } from "./ci-report-url";
94
+ import { ciTagSigner, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green";
95
+ import { loadRunnerKey } from "./trust/dsse";
70
96
  import { ciRedactor, ciSecretsFor, CI_SECRETS_ENV, readCiSecrets, type CiSecrets } from "./ci-secrets";
71
97
  import { updateCiRunIndex, type CiRunIndexEntry } from "./ci-run-index";
72
98
  import { resolveMemberContext } from "./member-pipeline";
73
99
  import { signOpRunRecord } from "./trust/op-run-statement";
74
100
  import { gitTop } from "./tree";
75
101
  import { hostRunnerKey } from "./ci-runner-key";
102
+ import { requireRunnerStart } from "./ci-runner-start";
103
+ import { securityGroupsAt } from "./security";
76
104
 
77
- /** The two jobs a pull-request pipeline has. */
105
+ /** The jobs a pull-request pipeline has: plan, and on merge record (#4168) or, opted in, apply. */
78
106
  export type CiJobName = PrLoopJob["jobName"];
79
- export const CI_JOB_NAMES: readonly CiJobName[] = ["plan", "apply"];
107
+ export const CI_JOB_NAMES: readonly CiJobName[] = ["plan", "apply", "record"];
80
108
 
81
109
  /** The Op each job is recorded as, which `ci.green` phases match. */
82
- export const CI_JOB_OPS: Record<CiJobName, string> = { plan: "pr-plan", apply: "pr-apply" };
110
+ export const CI_JOB_OPS: Record<CiJobName, string> = { plan: "pr-plan", apply: "pr-apply", record: "pr-record" };
111
+
112
+ /** What each job's Op says it does. */
113
+ const CI_JOB_OVERVIEWS: Record<CiJobName, string> = {
114
+ plan: "Plan the members this pull request reaches",
115
+ apply: "Apply the plan a reviewer approved",
116
+ record: "Record the plan on the merged commit; nothing applies",
117
+ };
83
118
 
84
119
  /** Where a job the chant runner ran says it ran (#4094). */
85
120
  export const CHANT_RUNNER_ORIGIN: RunOrigin = { forge: "chant" };
86
121
 
122
+ /** Where a job `chant ci run --local` ran says it ran (#4161): this machine, outside CI. */
123
+ export const CHANT_LOCAL_ORIGIN: RunOrigin = { forge: "local" };
124
+
87
125
  /** The runner's variables every job gets, when the runner has them. */
88
126
  export const CI_RUN_BASE_ENV = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TZ"] as const;
89
127
 
@@ -226,7 +264,7 @@ export function ciJobOp(input: CiJobOpInput): OpConfig {
226
264
  const op = CI_JOB_OPS[input.job.jobName];
227
265
  return {
228
266
  name: op,
229
- overview: input.job.jobName === "plan" ? "Plan the members this pull request reaches" : "Apply the plan a reviewer approved",
267
+ overview: CI_JOB_OVERVIEWS[input.job.jobName],
230
268
  labels: { Env: input.env, Pipeline: "pr", Job: input.job.jobName },
231
269
  phases,
232
270
  };
@@ -241,6 +279,17 @@ function exitCodeOf(error: string | undefined): number | undefined {
241
279
  return m ? Number(m[1]) : undefined;
242
280
  }
243
281
 
282
+ /**
283
+ * The code `chant ci run` exits with for a settled job: 0 when it passed,
284
+ * else its command's own code when that is 1 to 255, such as 3 for a moved
285
+ * plan or a waiting wave (#4156), else 1. The run index records the same
286
+ * code (#4165).
287
+ */
288
+ export function ciJobExitCode(result: { status: OpRunResult["status"]; exitCode?: number }): number {
289
+ if (result.status === "ok") return 0;
290
+ return result.exitCode && result.exitCode > 0 && result.exitCode < 256 ? result.exitCode : 1;
291
+ }
292
+
244
293
  /** The first line of a step's error, for a reason. */
245
294
  const firstLine = (text: string): string => text.split("\n").find((l) => l.trim() !== "")?.trim() ?? text;
246
295
 
@@ -313,12 +362,12 @@ const STATUS_DESCRIPTION = 140;
313
362
  const cut = (text: string, max = STATUS_DESCRIPTION): string => (text.length > max ? `${text.slice(0, max - 3)}...` : text);
314
363
 
315
364
  /**
316
- * A job's status context: `chant/pr-plan` for the plan, and for the apply
365
+ * A job's status context: `chant/pr-plan` for the plan, `chant/pr-record` for the record (#4168), and for the apply
317
366
  * `chant/<gate>`, which is `chant/pr-apply` unless `--gate` renamed the gate.
318
367
  * pr-plan's own status is `chant/plan` (../pr-loop.ts), a different context.
319
368
  */
320
369
  export function ciStatusContext(job: CiJobName, gate?: string): string {
321
- return job === "apply" ? `chant/${gate ?? PR_APPLY_GATE}` : `chant/${CI_JOB_OPS.plan}`;
370
+ return job === "apply" ? `chant/${gate ?? PR_APPLY_GATE}` : `chant/${CI_JOB_OPS[job]}`;
322
371
  }
323
372
 
324
373
  /**
@@ -378,7 +427,12 @@ export interface CiRunRequest {
378
427
  base?: string;
379
428
  pr?: number;
380
429
  commit?: string;
381
- /** The generator's options: `--gate`, `--branch`, `--events-json`, `--report-url`, `--pr-build` and `--pr-build-command`. */
430
+ /**
431
+ * The generator's options: `--gate`, `--branch`, `--events-json`,
432
+ * `--report-url`, `--pr-build` and `--pr-build-command`, and `--wave-gate`
433
+ * (#4003), with which the apply job runs the waves in order and stops at
434
+ * the first whose moved plan waits on its own gate.
435
+ */
382
436
  loop?: PrLoopPipelineOptions;
383
437
  /** `--image`: the image a generated pipeline names. Only the default gets setup lines. */
384
438
  image?: string;
@@ -425,6 +479,30 @@ export interface CiRunRequest {
425
479
  resumeRecord?: string;
426
480
  /** Set a commit status at the job's commit (#4097). Absent: none is set. `chant ci run` builds it from the environment. */
427
481
  status?: CiStatusConfig;
482
+ /**
483
+ * `--in-place` (#4156): run in the checkout `cwd` is in, at its `HEAD`, as
484
+ * a generated forge job does, with the runner's whole environment less
485
+ * `CHANT_RUNNER_KEY`, no setup phase, and the forge's run as `runOrigin`.
486
+ */
487
+ inPlace?: boolean;
488
+ /**
489
+ * `--local` (#4161): run on this machine and write nothing beyond it. It
490
+ * implies `push: false` and no status (`status` is ignored), writes
491
+ * nothing under `loop.reportUrl`, and the record and events say
492
+ * `runOrigin: { forge: "local" }` and `runtime: "local"`.
493
+ */
494
+ local?: boolean;
495
+ /**
496
+ * `--require-runner-start` (#4147): refuse the job before its first step
497
+ * unless a runner-start statement, signed by a runner key
498
+ * `.chant/trust.json` at `base` lists, names this forge job.
499
+ */
500
+ requireRunnerStart?: boolean;
501
+ /**
502
+ * `--wave <k>` (#4003, #4156): the one wave a generated apply job runs, as
503
+ * `pr-apply --wave <k>`. Needs `--wave-gate`.
504
+ */
505
+ wave?: number;
428
506
  signal?: AbortSignal;
429
507
  }
430
508
 
@@ -443,8 +521,14 @@ export interface CiRunResult {
443
521
  pushWarning?: string;
444
522
  /** What went wrong beside the job, such as a record the key could not sign. */
445
523
  warnings: string[];
524
+ /** What `--verbose` prints: such as a run left unsigned when nothing asks for a signature (#4156). */
525
+ notes: string[];
526
+ /** The exit code of the job's command when it failed with one, such as pr-apply's 3 for a moved plan. */
527
+ exitCode?: number;
446
528
  /** The checkout the job ran in, removed by now. */
447
529
  checkout: string;
530
+ /** The job ran with `local` (#4161): its record stays on this machine. */
531
+ local?: true;
448
532
  }
449
533
 
450
534
  const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
@@ -516,6 +600,11 @@ async function recordInIndex(input: {
516
600
  member?: string;
517
601
  pr?: number;
518
602
  record: OpRunRecord;
603
+ runOrigin: RunOrigin;
604
+ /** The runner that signed the record, or null (#4156). */
605
+ signer: string | null;
606
+ /** The code `chant ci run` exits with for the job (#4165). */
607
+ exitCode: number;
519
608
  reportDir: string;
520
609
  limit?: number;
521
610
  /** The run's redactor (#4096): every string written to the index goes through it, as the copied report does. */
@@ -547,7 +636,9 @@ async function recordInIndex(input: {
547
636
  ended: input.record.ended,
548
637
  runId: clean(input.record.id),
549
638
  key: clean(reportKey(pr, input.commit, input.member)),
550
- runOrigin: CHANT_RUNNER_ORIGIN,
639
+ runOrigin: input.runOrigin,
640
+ signer: input.signer,
641
+ exitCode: input.exitCode,
551
642
  };
552
643
  await updateCiRunIndex({ target, entry, ...(input.limit ? { limit: input.limit } : {}), warn: input.warn });
553
644
  }
@@ -565,11 +656,21 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
565
656
  const top = gitTop(cwd);
566
657
  if (!top) throw new CiRunError("a job runs in a fresh checkout of a commit, and this directory is not in a git repository");
567
658
  const commit = resolveJobCommit(top, req.commit ?? "HEAD");
659
+ if (req.inPlace && commit !== resolveJobCommit(top, "HEAD")) throw new CiRunError(`--in-place runs the checkout's HEAD, and --commit names ${commit.slice(0, 12)}`);
660
+ if (req.local && req.inPlace) 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");
568
661
  const place = ciJobPlace(cwd, top);
569
- const jobs = prLoopJobs(req.forge, req.env, req.loop ?? {}, place.member);
570
- const job = jobs.find((j) => j.jobName === req.job);
571
- if (!job) throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
572
- const jobEnv = ciJobEnv(job, {
662
+ const jobs = prLoopJobs(req.forge, req.env, req.local ? localLoop(req.loop) : (req.loop ?? {}), place.member);
663
+ const found = jobs.find((j) => j.jobName === req.job);
664
+ if (!found) throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
665
+ if (req.wave !== undefined && (req.job !== "apply" || !req.loop?.waveGate)) throw new CiRunError("--wave names the one wave an apply runs, and takes --wave-gate: ci run apply --wave-gate <gate> --wave <k>");
666
+ // #4147: before anything runs or any secret reaches a step, the provisioner's word that this runner was started for this job.
667
+ // #4168: under the isolated-runners security group an apply job requires the statement too, unless it runs on this machine.
668
+ const isolated = !req.requireRunnerStart && !req.local && req.job === "apply" && securityGroupsAt(cwd).has("isolated-runners");
669
+ const started = req.requireRunnerStart || isolated ? runnerStartFor(req, { top, commit, hostEnv }) : undefined;
670
+ // A generated wave job's command, as prLoopWaveJobs writes it. #4161: a local job's command posts no status or note.
671
+ const command = `${found.command}${req.wave !== undefined ? ` --wave ${req.wave}` : ""}${req.local ? " --no-status" : ""}`;
672
+ const job = { ...found, command, ...(req.wave !== undefined ? { wave: req.wave } : {}) };
673
+ const own = ciJobEnv(job, {
573
674
  forge: req.forge,
574
675
  env: req.env,
575
676
  values: { base: req.base, pr: req.pr, commit },
@@ -577,37 +678,107 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
577
678
  passEnv: req.passEnv,
578
679
  secrets: ciSecretsFor(req.secrets, req.job),
579
680
  });
681
+ // #4156: in place, the forge job's environment is the job's, less the runner key.
682
+ const jobEnv = req.inPlace ? { ...definedEnv(hostEnv), ...own } : own;
683
+ delete jobEnv[RUNNER_KEY_ENV];
580
684
  // #4096: the secrets and the job's secret-looking variables, redacted from everything the run writes and returns.
581
- const redact = ciRedactor(jobEnv, req.secrets);
685
+ // 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.
686
+ const redact = ciRedactor(req.inPlace ? { ...longValues(jobEnv), ...own } : jobEnv, req.secrets);
582
687
  try {
583
- const result = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
584
- return redact ? { ...result, warnings: result.warnings.map(redact), ...(result.pushWarning ? { pushWarning: redact(result.pushWarning) } : {}) } : result;
688
+ const ran = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
689
+ const result = started ? { ...ran, notes: [started, ...ran.notes] } : ran;
690
+ return redact ? { ...result, warnings: result.warnings.map(redact), notes: result.notes.map(redact), ...(result.pushWarning ? { pushWarning: redact(result.pushWarning) } : {}) } : result;
585
691
  } catch (err) {
586
692
  if (redact && err instanceof Error) err.message = redact(err.message);
587
693
  throw err;
588
694
  }
589
695
  }
590
696
 
697
+ /**
698
+ * `--require-runner-start` (#4147): the runner-start statement for this
699
+ * forge job, checked against `.chant/trust.json` at `--base` (the commit
700
+ * before the change, as a push's `before` is), or at the job's commit
701
+ * without one. Throws {@link CiRunError} unless it verifies.
702
+ */
703
+ function runnerStartFor(req: CiRunRequest, at: { top: string; commit: string; hostEnv: Record<string, string | undefined> }): string {
704
+ if (req.local) throw new CiRunError("--require-runner-start checks a forge job's runner, and --local runs the job on this machine");
705
+ const base = req.base ? resolveJobCommit(at.top, req.base) : at.commit;
706
+ try {
707
+ return requireRunnerStart({ top: at.top, base, commit: at.commit, env: at.hostEnv }).reason;
708
+ } catch (err) {
709
+ throw new CiRunError(message(err));
710
+ }
711
+ }
712
+
713
+ /** The generator's options for a `--local` job (#4161): the same, with no report URL. */
714
+ function localLoop(loop: PrLoopPipelineOptions | undefined): PrLoopPipelineOptions {
715
+ const { reportUrl: _dropped, ...rest } = loop ?? {};
716
+ return rest;
717
+ }
718
+
719
+ /** The variables of `env` whose value is long enough to be a credential, 8 characters or more. */
720
+ function longValues(env: Record<string, string>): Record<string, string> {
721
+ return Object.fromEntries(Object.entries(env).filter(([, value]) => value.length >= 8));
722
+ }
723
+
724
+ /** The variables of `env` that have a value. */
725
+ function definedEnv(env: Record<string, string | undefined>): Record<string, string> {
726
+ const out: Record<string, string> = {};
727
+ for (const [name, value] of Object.entries(env)) if (value !== undefined) out[name] = value;
728
+ return out;
729
+ }
730
+
731
+ /**
732
+ * Who signs the run (#4156): the runner key, checked against
733
+ * `.chant/trust.json` at the commit. A key that isn't an Ed25519 PEM is an
734
+ * error, and so is a `--key` the file does not list. A key from the
735
+ * environment that it does not list, and no key at all, leave the run
736
+ * unsigned: a warning when the declaration's `ci.green.signedSince` asks
737
+ * for signed runs, a note for `--verbose` otherwise.
738
+ */
739
+ function ciRunSigner(req: CiRunRequest, at: { cwd: string; top: string; commit: string }): { signer?: ReturnType<typeof ciTagSigner>; warnings: string[]; notes: string[] } {
740
+ // #4161: a local run never counts, signed or not, so nothing about its signature warns.
741
+ const required = req.local ? null : signedRunsRequired(at.cwd);
742
+ const say = (text: string) => (required ? { warnings: [`${text}, and ci.green.signedSince is ${required}, so it will not count`], notes: [] } : { warnings: [], notes: [text] });
743
+ if (req.runnerKey === undefined) return say("there is no runner key, so the run's record is unsigned");
744
+ try {
745
+ loadRunnerKey(req.runnerKey);
746
+ } catch (err) {
747
+ throw new CiRunError(`the runner key${req.runnerKeyFromEnv ? ` in ${RUNNER_KEY_ENV}` : ""} is not an Ed25519 private key in PEM: ${message(err)}`);
748
+ }
749
+ try {
750
+ return { signer: ciTagSigner(at.top, at.commit, req.runnerKey, "the runs it signed"), warnings: [], notes: [] };
751
+ } catch (err) {
752
+ if (!req.runnerKeyFromEnv) throw new CiRunError(`the run would not be signed: ${message(err)}`);
753
+ return say(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
754
+ }
755
+ }
756
+
757
+ /** Where the job runs: a fresh worktree of the commit, or with `inPlace` the checkout itself, which is left as it is. */
758
+ function jobCheckout(top: string, commit: string, inPlace?: boolean): { dir: string; remove(): void } {
759
+ return inPlace ? { dir: top, remove() {} } : freshCheckout(top, commit);
760
+ }
761
+
591
762
  /** {@link ciRun} once the job and its environment are known. */
592
763
  async function runCiJob(
593
764
  req: CiRunRequest,
594
765
  at: { cwd: string; top: string; commit: string; place: { member?: string; dir: string }; job: PrLoopJob; jobEnv: Record<string, string>; redact?: Redact },
595
766
  ): Promise<CiRunResult> {
596
767
  const { cwd, top, commit, place, job, jobEnv, redact } = at;
768
+ const local = req.local === true;
769
+ const status = local ? undefined : req.status;
770
+ const reportUrl = local ? undefined : req.loop?.reportUrl;
597
771
 
598
- // 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.
599
- let signer: ReturnType<typeof ciTagSigner> | undefined;
600
- const early: string[] = [];
601
- if (req.runnerKey !== undefined) {
602
- try {
603
- signer = ciTagSigner(top, commit, req.runnerKey, "the runs it signed");
604
- } catch (err) {
605
- if (!req.runnerKeyFromEnv) throw new CiRunError(`the run would not be signed: ${message(err)}`);
606
- early.push(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
607
- }
608
- }
772
+ // 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.
773
+ const signing = ciRunSigner(req, { cwd, top, commit });
774
+ const signer = signing.signer;
775
+ const early = signing.warnings;
776
+ const notes = signing.notes;
777
+ const origin: RunOrigin = local ? CHANT_LOCAL_ORIGIN : req.inPlace ? (resolveCiJob(req.hostEnv ?? process.env)?.runOrigin ?? CHANT_RUNNER_ORIGIN) : CHANT_RUNNER_ORIGIN;
778
+ // In place, the job writes its record on the checkout's chant/lifecycle, which a forge checkout may not have fetched.
779
+ if (req.inPlace && req.push !== false) await fetchLifecycleStatus({ cwd: top }).catch(() => undefined);
609
780
 
610
- const checkout = freshCheckout(top, commit);
781
+ const checkout = jobCheckout(top, commit, req.inPlace);
611
782
  try {
612
783
  const jobDir = place.dir === "." ? checkout.dir : join(checkout.dir, place.dir);
613
784
  const op = ciJobOp({
@@ -615,7 +786,7 @@ async function runCiJob(
615
786
  env: req.env,
616
787
  cwd: jobDir,
617
788
  jobEnv,
618
- setup: prLoopSetup(req.image ?? PR_LOOP_IMAGE),
789
+ setup: req.inPlace ? [] : prLoopSetup(req.image ?? PR_LOOP_IMAGE),
619
790
  build: [...(req.beforeScript ?? []), ...(job.build ?? [])],
620
791
  ...(req.chant ? { chant: req.chant } : {}),
621
792
  });
@@ -627,13 +798,13 @@ async function runCiJob(
627
798
  copyFileSync(resume.host, resume.job);
628
799
  }
629
800
  const statusContext = ciStatusContext(req.job, req.loop?.gate);
630
- const statusUrl = req.status
631
- ? 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 } : {}) })
801
+ const statusUrl = status
802
+ ? ciStatusUrl({ ...(status.urlTemplate ? { urlTemplate: status.urlTemplate } : {}), ...(reportUrl ? { reportUrl } : {}), ...(req.pr !== undefined ? { pr: req.pr } : {}), commit, ...(place.member ? { member: place.member } : {}) })
632
803
  : undefined;
633
804
  const postStatus = async (state: CommitStatus["state"], description: string): Promise<void> => {
634
- if (!req.status) return;
805
+ if (!status) return;
635
806
  try {
636
- await req.status.client.setCommitStatus(commit, { context: statusContext, state, description: cut(redact ? redact(description) : description), ...(statusUrl ? { targetUrl: statusUrl } : {}) });
807
+ await status.client.setCommitStatus(commit, { context: statusContext, state, description: cut(redact ? redact(description) : description), ...(statusUrl ? { targetUrl: statusUrl } : {}) });
637
808
  } catch (err) {
638
809
  warnings.push(`could not set the ${statusContext} status: ${message(err)}`);
639
810
  }
@@ -649,7 +820,7 @@ async function runCiJob(
649
820
  cwd: jobDir,
650
821
  commit,
651
822
  ledger: { cwd: jobDir },
652
- runner: { kind: "pipeline", runtime: "ci", runOrigin: CHANT_RUNNER_ORIGIN },
823
+ runner: { kind: "pipeline", runtime: local ? "local" : "ci", runOrigin: origin },
653
824
  describeFailure: (records) => ciJobFailure({ job: req.job, op, records, reportDir: join(jobDir, PR_LOOP_REPORT_DIR) }),
654
825
  ...(req.runEvents ? { runEvents: { write: req.runEvents } } : {}),
655
826
  ...(req.stepOutput ? { stepOutput: req.stepOutput } : {}),
@@ -687,26 +858,37 @@ async function runCiJob(
687
858
  let report: string | null = null;
688
859
  if (existsSync(written)) {
689
860
  report = resolve(cwd, req.output ?? PR_LOOP_REPORT_DIR);
690
- cpSync(written, report, { recursive: true });
691
- if (redact) redactTree(report, redact);
692
- }
693
- if (req.loop?.reportUrl) {
694
- 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) });
861
+ // In place the report is already where the forge keeps it, and stays as the command wrote it.
862
+ if (!(existsSync(report) && realpathSync(report) === realpathSync(written))) {
863
+ cpSync(written, report, { recursive: true });
864
+ if (redact) redactTree(report, redact);
865
+ }
695
866
  }
696
- if (ledgerError) throw new CiRunError(ledgerError);
697
- const pushed: { pushed: boolean; pushWarning?: string } = req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
698
867
  const failed = result.records.find((r) => r.status === "fail");
868
+ const exitCode = failed?.phase === req.job ? exitCodeOf(failed.error) : undefined;
869
+ if (reportUrl) {
870
+ 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) });
871
+ }
872
+ // In place the forge job's outcome is the command's, so a record that could not be written is a warning there.
873
+ if (ledgerError && req.inPlace) warnings.push(ledgerError);
874
+ else if (ledgerError) throw new CiRunError(ledgerError);
875
+ const pushed: { pushed: boolean; pushWarning?: string } = local
876
+ ? { pushed: false }
877
+ : req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
699
878
  return {
700
879
  op: op.name,
701
880
  status: result.status,
702
881
  record: result.record,
703
882
  ...(failed ? { failure: { phase: failed.phase, error: failed.error ?? "the step failed" } } : {}),
883
+ ...(exitCode !== undefined ? { exitCode } : {}),
704
884
  report,
705
- signer: signer?.principal ?? null,
885
+ signer: result.record.statement && signer ? signer.principal : null,
706
886
  pushed: pushed.pushed,
707
887
  ...(pushed.pushWarning ? { pushWarning: pushed.pushWarning } : {}),
708
888
  warnings,
889
+ notes,
709
890
  checkout: checkout.dir,
891
+ ...(local ? { local: true as const } : {}),
710
892
  };
711
893
  } finally {
712
894
  checkout.remove();
@@ -740,19 +922,40 @@ export function formatCiRun(r: CiRunResult): string {
740
922
  const why = r.record.failure;
741
923
  if (why) lines.push(` failure: ${why.code}${why.phase ? ` in ${why.phase}` : ""}${why.exitCode !== undefined ? `, exit ${why.exitCode}` : ""}: ${why.reason}`);
742
924
  else if (r.failure) lines.push(` ${r.failure.phase}: ${r.failure.error}`);
743
- 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"}`);
925
+ lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned"}`);
744
926
  if (r.report) lines.push(` report: ${r.report}`);
745
- if (!r.pushed) lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
927
+ if (r.local) lines.push(" local run: the record stays on this machine, with no push, commit status or report upload");
928
+ else if (!r.pushed) lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
746
929
  return lines.join("\n");
747
930
  }
748
931
 
749
932
  // ── The command ──────────────────────────────────────────────────────────────
750
933
 
934
+ /**
935
+ * The generator's options from `ci run`'s and `ci serve`'s flags, as `chant
936
+ * build --components --generate <lexicon> --pr-loop` takes them: `--gate`,
937
+ * `--branch`, `--events-json`, `--report-url`, `--pr-build`,
938
+ * `--pr-build-command`, `--wave-gate` (#4003) and `--require-review`
939
+ * (#4168; `--no-require-review`, the default, is still accepted).
940
+ */
941
+ export function ciLoopOptions(args: CommandContext["args"]): PrLoopPipelineOptions {
942
+ return {
943
+ ...(args.gate ? { gate: args.gate } : {}),
944
+ ...(args.branch ? { branch: args.branch } : {}),
945
+ ...(args.eventsJson ? { events: true } : {}),
946
+ ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
947
+ ...(args.prBuild ? { build: true } : {}),
948
+ ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
949
+ ...(args.waveGate ? { waveGate: args.waveGate } : {}),
950
+ ...(args.requireReview === true ? { requireReview: true } : {}),
951
+ };
952
+ }
953
+
751
954
  const USAGE =
752
- "chant ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
753
- "[--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--run-index-limit <n>] [--image <image>] [--before-script <line>]... " +
754
- "[--pr-build] [--pr-build-command <line>] [--no-status] [--status-url <url template>] " +
755
- "[--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>]";
955
+ "chant ci run plan|record|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
956
+ "[--gate <name>] [--wave-gate <name>] [--wave <k>] [--branch <name>] [--events-json] [--report-url <url>] [--run-index-limit <n>] [--image <image>] [--before-script <line>]... " +
957
+ "[--pr-build] [--pr-build-command <line>] [--require-review] [--no-status] [--status-url <url template>] " +
958
+ "[--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>]";
756
959
 
757
960
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
758
961
 
@@ -775,13 +978,38 @@ export function ciSecretsOf(flag: string | undefined, env: Record<string, string
775
978
  return file === undefined ? undefined : readCiSecrets(resolve(file));
776
979
  }
777
980
 
778
- /** `chant ci run plan|apply` (#4094). */
981
+ /**
982
+ * `chant ci run --at <sha>` (#4169): ask for every required phase to be built
983
+ * at a commit of the green branch. On GitHub, with a token in the
984
+ * environment, the workflows holding the phases start at once.
985
+ */
986
+ async function requestBuildCommand(at: string): Promise<number> {
987
+ const { requestBuild } = await import("./ci-green");
988
+ const { ciForgeFromEnv } = await import("./ci-green-forge");
989
+ let forge: Awaited<ReturnType<typeof ciForgeFromEnv>> | undefined;
990
+ try {
991
+ forge = process.env.GITHUB_REPOSITORY ? ciForgeFromEnv("github") : undefined;
992
+ } catch {
993
+ forge = undefined;
994
+ }
995
+ const r = await requestBuild({ cwd: process.cwd(), at, ...(forge ? { forge } : {}) });
996
+ for (const w of r.warnings) console.error(formatWarning({ message: w }));
997
+ const what = `${r.request.phases.join(", ")} at ${r.request.commit.slice(0, 12)}`;
998
+ console.log(
999
+ `${r.existed ? `${r.request.tag} already asks for a build` : `Asked for ${what}: ${r.request.tag}`}. ` +
1000
+ (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."),
1001
+ );
1002
+ return 0;
1003
+ }
1004
+
1005
+ /** `chant ci run plan|record|apply` (#4094, #4168). */
779
1006
  export async function runCiRun(ctx: CommandContext): Promise<number> {
780
1007
  const { args } = ctx;
781
1008
  try {
782
1009
  if (args.setupKey) return (await import("./ci-serve")).runnerKeySetupCommand(ctx);
1010
+ if (args.at !== undefined && args.extraPositional === undefined) return await requestBuildCommand(args.at);
783
1011
  const job = args.extraPositional;
784
- if (job !== "plan" && job !== "apply") throw new CiRunError(`ci run takes the job to run, plan or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
1012
+ if (job !== "plan" && job !== "apply" && job !== "record") throw new CiRunError(`ci run takes the job to run, plan, record or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
785
1013
  if (!args.env) throw new CiRunError("--env names the environment the pipeline is for, as chant build --components --generate <lexicon> --pr-loop took it: --env <env>");
786
1014
  const forge = args.forge ?? detectForge(process.env);
787
1015
  if (!forge) throw new CiRunError("--forge names the forge the job posts to: --forge github|gitlab|forgejo");
@@ -792,9 +1020,13 @@ export async function runCiRun(ctx: CommandContext): Promise<number> {
792
1020
  const hostKey = fromFlag || process.env[RUNNER_KEY_ENV] ? undefined : hostRunnerKeyAt(process.cwd(), args.statePath);
793
1021
  const runnerKey = fromFlag ? readFileSync(resolve(args.key!), "utf-8") : process.env[RUNNER_KEY_ENV] || hostKey?.pem;
794
1022
  const hostEnv = process.env;
795
- const { status, warning } = ciStatusFromEnv({ forge: forge as ForgeKind, hostEnv, noStatus: args.noStatus, statusUrl: args.statusUrl });
1023
+ // #4156: in place the forge shows the job, so ci run sets no status of its own.
1024
+ // #4161: a local run sets no status, so it builds no client and warns about none.
1025
+ const { status, warning } = ciStatusFromEnv({ forge: forge as ForgeKind, hostEnv, noStatus: args.noStatus || args.ciInPlace || args.local, statusUrl: args.statusUrl });
796
1026
  if (warning) console.error(formatWarning({ message: warning }));
797
1027
  const secrets = ciSecretsOf(args.ciSecrets);
1028
+ // #4160: without --report-url, the declaration's ci.reportUrl.
1029
+ const reportUrl = await reportUrlOf(args.reportUrl, process.cwd());
798
1030
  const result = await ciRun({
799
1031
  cwd: process.cwd(),
800
1032
  ...(status ? { status } : {}),
@@ -804,14 +1036,12 @@ export async function runCiRun(ctx: CommandContext): Promise<number> {
804
1036
  ...(args.base ? { base: args.base } : {}),
805
1037
  ...(args.pr !== undefined ? { pr: args.pr } : {}),
806
1038
  ...(args.commit ? { commit: args.commit } : {}),
807
- loop: {
808
- ...(args.gate ? { gate: args.gate } : {}),
809
- ...(args.branch ? { branch: args.branch } : {}),
810
- ...(args.eventsJson ? { events: true } : {}),
811
- ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
812
- ...(args.prBuild ? { build: true } : {}),
813
- ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
814
- },
1039
+ loop: ciLoopOptions(reportUrl ? { ...args, reportUrl } : args),
1040
+ ...(args.ciInPlace ? { inPlace: true } : {}),
1041
+ ...(args.local ? { local: true } : {}),
1042
+ ...(args.noPush ? { push: false } : {}),
1043
+ ...(args.wave !== undefined ? { wave: args.wave } : {}),
1044
+ ...(args.requireRunnerStart ? { requireRunnerStart: true } : {}),
815
1045
  ...(args.runIndexLimit ? { runIndexLimit: args.runIndexLimit } : {}),
816
1046
  ...(args.image ? { image: args.image } : {}),
817
1047
  ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
@@ -824,8 +1054,10 @@ export async function runCiRun(ctx: CommandContext): Promise<number> {
824
1054
  stepOutput: (line) => void process.stderr.write(`${line}\n`),
825
1055
  });
826
1056
  for (const w of result.warnings) console.error(formatWarning({ message: w }));
1057
+ if (args.verbose) for (const n of result.notes) console.error(`ci run: ${n}`);
827
1058
  console.error(formatCiRun(result));
828
- return result.status === "ok" ? 0 : 1;
1059
+ // #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.
1060
+ return ciJobExitCode(result);
829
1061
  } catch (err) {
830
1062
  console.error(formatError({ message: `chant ci run: ${message(err)}`, hint: USAGE }));
831
1063
  return 1;