@intentius/chant 0.124.28 → 0.124.30

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 (449) hide show
  1. package/dist/build-params.d.ts +34 -1
  2. package/dist/build-params.d.ts.map +1 -1
  3. package/dist/build-params.js +31 -1
  4. package/dist/change-set-graph.d.ts +8 -1
  5. package/dist/change-set-graph.d.ts.map +1 -1
  6. package/dist/change-set-graph.js +4 -2
  7. package/dist/change-set.d.ts +10 -0
  8. package/dist/change-set.d.ts.map +1 -1
  9. package/dist/change-set.js +4 -3
  10. package/dist/cli/build-params-cli.d.ts +7 -1
  11. package/dist/cli/build-params-cli.d.ts.map +1 -1
  12. package/dist/cli/build-params-cli.js +17 -8
  13. package/dist/cli/commands/build.d.ts.map +1 -1
  14. package/dist/cli/commands/build.js +6 -3
  15. package/dist/cli/handlers/approve.d.ts.map +1 -1
  16. package/dist/cli/handlers/approve.js +45 -0
  17. package/dist/cli/handlers/build.d.ts.map +1 -1
  18. package/dist/cli/handlers/build.js +18 -4
  19. package/dist/cli/handlers/fan-out.d.ts +9 -0
  20. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  21. package/dist/cli/handlers/fan-out.js +67 -8
  22. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  23. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  24. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  25. package/dist/cli/handlers/pr.d.ts +11 -3
  26. package/dist/cli/handlers/pr.d.ts.map +1 -1
  27. package/dist/cli/handlers/pr.js +134 -26
  28. package/dist/cli/handlers/promote.d.ts +7 -0
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/promote.js +210 -33
  31. package/dist/cli/handlers/release.d.ts +62 -5
  32. package/dist/cli/handlers/release.d.ts.map +1 -1
  33. package/dist/cli/handlers/release.js +414 -28
  34. package/dist/cli/handlers/run.d.ts +11 -1
  35. package/dist/cli/handlers/run.d.ts.map +1 -1
  36. package/dist/cli/handlers/run.js +67 -6
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +91 -10
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +73 -2
  41. package/dist/cli/registry.d.ts +32 -4
  42. package/dist/cli/registry.d.ts.map +1 -1
  43. package/dist/cli/run-events.d.ts +3 -2
  44. package/dist/cli/run-events.d.ts.map +1 -1
  45. package/dist/cli/run-events.js +5 -3
  46. package/dist/components/auto-release.d.ts +2 -0
  47. package/dist/components/auto-release.d.ts.map +1 -1
  48. package/dist/components/auto-release.js +1 -0
  49. package/dist/components/capability.d.ts +9 -0
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/driver.d.ts +8 -0
  52. package/dist/components/driver.d.ts.map +1 -1
  53. package/dist/components/driver.js +35 -0
  54. package/dist/components/fan-out-inputs.d.ts +69 -0
  55. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  56. package/dist/components/fan-out-inputs.js +169 -0
  57. package/dist/components/fan-out-record.d.ts +13 -0
  58. package/dist/components/fan-out-record.d.ts.map +1 -1
  59. package/dist/components/fan-out-record.js +16 -0
  60. package/dist/components/fan-out-run.d.ts.map +1 -1
  61. package/dist/components/fan-out-run.js +50 -0
  62. package/dist/components/fan-out.d.ts +4 -0
  63. package/dist/components/fan-out.d.ts.map +1 -1
  64. package/dist/components/pr-pipeline.d.ts +49 -0
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +38 -4
  67. package/dist/components/pr-run.d.ts +20 -2
  68. package/dist/components/pr-run.d.ts.map +1 -1
  69. package/dist/components/pr-run.js +26 -13
  70. package/dist/components/promote.d.ts +23 -0
  71. package/dist/components/promote.d.ts.map +1 -1
  72. package/dist/components/promote.js +60 -2
  73. package/dist/components/release-compile.d.ts +9 -0
  74. package/dist/components/release-compile.d.ts.map +1 -1
  75. package/dist/components/release-compile.js +9 -3
  76. package/dist/components/release-gate.d.ts +11 -1
  77. package/dist/components/release-gate.d.ts.map +1 -1
  78. package/dist/components/release-gate.js +10 -4
  79. package/dist/components/wave-plan.d.ts +16 -0
  80. package/dist/components/wave-plan.d.ts.map +1 -1
  81. package/dist/components/wave-plan.js +36 -2
  82. package/dist/config.d.ts +38 -0
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +2 -0
  85. package/dist/flag-params.d.ts +54 -0
  86. package/dist/flag-params.d.ts.map +1 -0
  87. package/dist/flag-params.js +130 -0
  88. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  89. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  90. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  91. package/dist/lifecycle/change-set-store.d.ts +2 -1
  92. package/dist/lifecycle/change-set-store.d.ts.map +1 -1
  93. package/dist/lifecycle/change-set-store.js +2 -1
  94. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  95. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  96. package/dist/lifecycle/delivery-metrics.js +145 -0
  97. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  98. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  99. package/dist/lifecycle/deploy-outcome.js +101 -0
  100. package/dist/lifecycle/gate-change-set.d.ts +10 -1
  101. package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
  102. package/dist/lifecycle/gate-change-set.js +35 -3
  103. package/dist/lifecycle/gate-ledger.d.ts +2 -3
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +30 -1
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/git.js +31 -5
  108. package/dist/lifecycle/index.d.ts +4 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/index.js +4 -0
  111. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  112. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  113. package/dist/lifecycle/mixed-environment.js +154 -0
  114. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  115. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  116. package/dist/lifecycle/publish-attachments.js +179 -0
  117. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  118. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  119. package/dist/lifecycle/publish-credentials.js +69 -0
  120. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  121. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  122. package/dist/lifecycle/publish-declaration.js +83 -0
  123. package/dist/lifecycle/publish-forge.d.ts +53 -0
  124. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  125. package/dist/lifecycle/publish-forge.js +298 -0
  126. package/dist/lifecycle/publish-npm.d.ts +61 -0
  127. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  128. package/dist/lifecycle/publish-npm.js +242 -0
  129. package/dist/lifecycle/publish-oci.d.ts +64 -0
  130. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  131. package/dist/lifecycle/publish-oci.js +383 -0
  132. package/dist/lifecycle/publish-record.d.ts +66 -0
  133. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  134. package/dist/lifecycle/publish-record.js +84 -0
  135. package/dist/lifecycle/publish-targets.d.ts +45 -0
  136. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  137. package/dist/lifecycle/publish-targets.js +85 -0
  138. package/dist/lifecycle/release-cut.d.ts +48 -1
  139. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  140. package/dist/lifecycle/release-cut.js +116 -23
  141. package/dist/lifecycle/release-join.d.ts +95 -0
  142. package/dist/lifecycle/release-join.d.ts.map +1 -0
  143. package/dist/lifecycle/release-join.js +139 -0
  144. package/dist/lifecycle/release-ledger.d.ts +8 -3
  145. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  146. package/dist/lifecycle/release-ledger.js +2 -0
  147. package/dist/lifecycle/release-publish.d.ts +312 -0
  148. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  149. package/dist/lifecycle/release-publish.js +335 -0
  150. package/dist/lifecycle/release-revocation.d.ts +80 -0
  151. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  152. package/dist/lifecycle/release-revocation.js +123 -0
  153. package/dist/lifecycle/release-store.d.ts +12 -2
  154. package/dist/lifecycle/release-store.d.ts.map +1 -1
  155. package/dist/lifecycle/release-store.js +4 -2
  156. package/dist/lifecycle/release-verify.d.ts +61 -0
  157. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  158. package/dist/lifecycle/release-verify.js +92 -0
  159. package/dist/lifecycle/release-workstation.d.ts +91 -0
  160. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  161. package/dist/lifecycle/release-workstation.js +180 -0
  162. package/dist/lifecycle/retention-policy.d.ts +45 -0
  163. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  164. package/dist/lifecycle/retention-policy.js +43 -0
  165. package/dist/lifecycle/retention.d.ts +185 -0
  166. package/dist/lifecycle/retention.d.ts.map +1 -0
  167. package/dist/lifecycle/retention.js +400 -0
  168. package/dist/lifecycle/run-ledger.d.ts +1 -1
  169. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/run-ledger.js +9 -0
  171. package/dist/op/activities/activity-contracts.d.ts +1 -0
  172. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  173. package/dist/op/activities/activity-contracts.js +1 -0
  174. package/dist/op/activities/shell.d.ts +6 -0
  175. package/dist/op/activities/shell.d.ts.map +1 -1
  176. package/dist/op/activities/shell.js +1 -1
  177. package/dist/op/local-executor.d.ts +23 -1
  178. package/dist/op/local-executor.d.ts.map +1 -1
  179. package/dist/op/local-executor.js +9 -3
  180. package/dist/op/runtime.d.ts +27 -0
  181. package/dist/op/runtime.d.ts.map +1 -1
  182. package/dist/op/runtime.js +20 -0
  183. package/dist/plan-summary.d.ts.map +1 -1
  184. package/dist/plan-summary.js +8 -2
  185. package/dist/pr-forge.d.ts +23 -0
  186. package/dist/pr-forge.d.ts.map +1 -1
  187. package/dist/pr-forge.js +44 -0
  188. package/dist/pr-loop.d.ts +17 -0
  189. package/dist/pr-loop.d.ts.map +1 -1
  190. package/dist/pr-loop.js +30 -4
  191. package/dist/pr-report-url.d.ts +82 -0
  192. package/dist/pr-report-url.d.ts.map +1 -0
  193. package/dist/pr-report-url.js +174 -0
  194. package/dist/provenance.d.ts +2 -2
  195. package/dist/provenance.d.ts.map +1 -1
  196. package/dist/run-event-store.d.ts +29 -2
  197. package/dist/run-event-store.d.ts.map +1 -1
  198. package/dist/run-event-store.js +50 -3
  199. package/dist/run-event-stream.d.ts +4 -3
  200. package/dist/run-event-stream.d.ts.map +1 -1
  201. package/dist/run-event-stream.js +7 -3
  202. package/dist/run-event.d.ts +34 -1
  203. package/dist/run-event.d.ts.map +1 -1
  204. package/dist/run-event.js +4 -0
  205. package/dist/wave-runtime.mjs +260 -229
  206. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  207. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  208. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  209. package/dist/workspace/change-set.schema.json +5 -1
  210. package/dist/workspace/check.schema.json +4 -2
  211. package/dist/workspace/checks/records.d.ts +9 -1
  212. package/dist/workspace/checks/records.d.ts.map +1 -1
  213. package/dist/workspace/checks/records.js +35 -1
  214. package/dist/workspace/checks.d.ts +1 -0
  215. package/dist/workspace/checks.d.ts.map +1 -1
  216. package/dist/workspace/checks.js +2 -1
  217. package/dist/workspace/ci-green-forge.d.ts +2 -0
  218. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  219. package/dist/workspace/ci-green.d.ts +10 -0
  220. package/dist/workspace/ci-green.d.ts.map +1 -1
  221. package/dist/workspace/ci-green.js +14 -5
  222. package/dist/workspace/ci-run.d.ts +222 -0
  223. package/dist/workspace/ci-run.d.ts.map +1 -0
  224. package/dist/workspace/ci-run.js +464 -0
  225. package/dist/workspace/ci-serve.d.ts +214 -0
  226. package/dist/workspace/ci-serve.d.ts.map +1 -0
  227. package/dist/workspace/ci-serve.js +412 -0
  228. package/dist/workspace/declaration.d.ts +16 -0
  229. package/dist/workspace/declaration.d.ts.map +1 -1
  230. package/dist/workspace/declaration.js +26 -0
  231. package/dist/workspace/declaration.schema.json +64 -0
  232. package/dist/workspace/declared-kinds.d.ts +16 -0
  233. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  234. package/dist/workspace/declared-kinds.js +25 -0
  235. package/dist/workspace/delivery-metrics.d.ts +91 -0
  236. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  237. package/dist/workspace/delivery-metrics.js +217 -0
  238. package/dist/workspace/delivery-metrics.schema.json +519 -0
  239. package/dist/workspace/flags-read.d.ts +37 -0
  240. package/dist/workspace/flags-read.d.ts.map +1 -0
  241. package/dist/workspace/flags-read.js +103 -0
  242. package/dist/workspace/flags.d.ts +100 -0
  243. package/dist/workspace/flags.d.ts.map +1 -0
  244. package/dist/workspace/flags.js +98 -0
  245. package/dist/workspace/graph-cli.js +1 -1
  246. package/dist/workspace/intent-cli.d.ts +11 -2
  247. package/dist/workspace/intent-cli.d.ts.map +1 -1
  248. package/dist/workspace/intent-cli.js +54 -5
  249. package/dist/workspace/intent-trace.d.ts +219 -0
  250. package/dist/workspace/intent-trace.d.ts.map +1 -0
  251. package/dist/workspace/intent-trace.js +314 -0
  252. package/dist/workspace/intent.d.ts +30 -6
  253. package/dist/workspace/intent.d.ts.map +1 -1
  254. package/dist/workspace/intent.js +141 -21
  255. package/dist/workspace/intent.schema.json +702 -6
  256. package/dist/workspace/overlap.d.ts +146 -0
  257. package/dist/workspace/overlap.d.ts.map +1 -0
  258. package/dist/workspace/overlap.js +276 -0
  259. package/dist/workspace/pr-report.schema.json +5 -0
  260. package/dist/workspace/reason-codes.d.ts +2 -0
  261. package/dist/workspace/reason-codes.d.ts.map +1 -1
  262. package/dist/workspace/reason-codes.js +3 -0
  263. package/dist/workspace/records.d.ts +15 -2
  264. package/dist/workspace/records.d.ts.map +1 -1
  265. package/dist/workspace/records.js +32 -0
  266. package/dist/workspace/records.schema.json +21 -2
  267. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  268. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  269. package/dist/workspace/releases.d.ts +9 -0
  270. package/dist/workspace/releases.d.ts.map +1 -1
  271. package/dist/workspace/releases.js +9 -0
  272. package/dist/workspace/releases.schema.json +123 -1
  273. package/dist/workspace/run-event.schema.json +24 -6
  274. package/dist/workspace/status-gates.d.ts +8 -0
  275. package/dist/workspace/status-gates.d.ts.map +1 -1
  276. package/dist/workspace/status-stewards.d.ts +2 -0
  277. package/dist/workspace/status-stewards.d.ts.map +1 -1
  278. package/dist/workspace/status-stewards.js +1 -0
  279. package/dist/workspace/status.d.ts +78 -0
  280. package/dist/workspace/status.d.ts.map +1 -1
  281. package/dist/workspace/status.js +170 -3
  282. package/dist/workspace/status.schema.json +210 -2
  283. package/dist/workspace/wip.d.ts.map +1 -1
  284. package/dist/workspace/wip.js +11 -3
  285. package/package.json +1 -1
  286. package/src/build-params.test.ts +46 -0
  287. package/src/build-params.ts +55 -1
  288. package/src/change-set-graph.ts +10 -2
  289. package/src/change-set.ts +14 -3
  290. package/src/cli/build-params-cli.ts +22 -9
  291. package/src/cli/commands/build.ts +6 -3
  292. package/src/cli/handlers/approve-release.test.ts +5 -1
  293. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  294. package/src/cli/handlers/approve.ts +47 -0
  295. package/src/cli/handlers/build.test.ts +30 -0
  296. package/src/cli/handlers/build.ts +20 -4
  297. package/src/cli/handlers/fan-out.test.ts +262 -1
  298. package/src/cli/handlers/fan-out.ts +72 -8
  299. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  300. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  301. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  302. package/src/cli/handlers/pr.test.ts +148 -1
  303. package/src/cli/handlers/pr.ts +144 -28
  304. package/src/cli/handlers/promote.test.ts +280 -2
  305. package/src/cli/handlers/promote.ts +210 -34
  306. package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
  307. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  308. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  309. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  310. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  311. package/src/cli/handlers/release.e2e.test.ts +146 -32
  312. package/src/cli/handlers/release.ts +415 -28
  313. package/src/cli/handlers/run-auto-release.test.ts +9 -0
  314. package/src/cli/handlers/run-release.test.ts +55 -0
  315. package/src/cli/handlers/run.ts +69 -5
  316. package/src/cli/main.test.ts +1 -0
  317. package/src/cli/main.ts +91 -10
  318. package/src/cli/parse-args.ts +54 -2
  319. package/src/cli/registry.ts +32 -4
  320. package/src/cli/run-events.ts +5 -2
  321. package/src/components/auto-release.test.ts +9 -0
  322. package/src/components/auto-release.ts +3 -0
  323. package/src/components/capability.ts +9 -0
  324. package/src/components/driver.test.ts +24 -0
  325. package/src/components/driver.ts +36 -0
  326. package/src/components/fan-out-inputs.test.ts +85 -0
  327. package/src/components/fan-out-inputs.ts +192 -0
  328. package/src/components/fan-out-record.test.ts +10 -0
  329. package/src/components/fan-out-record.ts +19 -0
  330. package/src/components/fan-out-run.ts +48 -0
  331. package/src/components/fan-out.ts +4 -0
  332. package/src/components/pr-pipeline.ts +67 -4
  333. package/src/components/pr-run.test.ts +127 -0
  334. package/src/components/pr-run.ts +32 -12
  335. package/src/components/promote.ts +68 -2
  336. package/src/components/release-compile.ts +17 -3
  337. package/src/components/release-gate.ts +18 -4
  338. package/src/components/wave-plan.ts +45 -2
  339. package/src/config.ts +8 -0
  340. package/src/flag-params.test.ts +144 -0
  341. package/src/flag-params.ts +134 -0
  342. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  343. package/src/lifecycle/change-set-store.ts +2 -1
  344. package/src/lifecycle/delivery-metrics.ts +263 -0
  345. package/src/lifecycle/deploy-outcome.ts +147 -0
  346. package/src/lifecycle/gate-change-set.ts +37 -6
  347. package/src/lifecycle/gate-ledger.ts +2 -2
  348. package/src/lifecycle/git.ts +61 -8
  349. package/src/lifecycle/index.ts +4 -0
  350. package/src/lifecycle/mixed-environment.test.ts +97 -0
  351. package/src/lifecycle/mixed-environment.ts +204 -0
  352. package/src/lifecycle/publish-attachments.ts +196 -0
  353. package/src/lifecycle/publish-credentials.ts +84 -0
  354. package/src/lifecycle/publish-declaration.ts +95 -0
  355. package/src/lifecycle/publish-forge.ts +325 -0
  356. package/src/lifecycle/publish-npm.ts +259 -0
  357. package/src/lifecycle/publish-oci.ts +396 -0
  358. package/src/lifecycle/publish-record.ts +113 -0
  359. package/src/lifecycle/publish-resume.test.ts +168 -0
  360. package/src/lifecycle/publish-targets.test.ts +494 -0
  361. package/src/lifecycle/publish-targets.ts +100 -0
  362. package/src/lifecycle/release-cut.test.ts +74 -2
  363. package/src/lifecycle/release-cut.ts +158 -22
  364. package/src/lifecycle/release-flag.test.ts +112 -0
  365. package/src/lifecycle/release-join.test.ts +68 -0
  366. package/src/lifecycle/release-join.ts +182 -0
  367. package/src/lifecycle/release-ledger.ts +9 -3
  368. package/src/lifecycle/release-publish.test.ts +225 -0
  369. package/src/lifecycle/release-publish.ts +568 -0
  370. package/src/lifecycle/release-revocation.ts +147 -0
  371. package/src/lifecycle/release-store.ts +15 -4
  372. package/src/lifecycle/release-verify.test.ts +110 -0
  373. package/src/lifecycle/release-verify.ts +120 -0
  374. package/src/lifecycle/release-workstation.test.ts +204 -0
  375. package/src/lifecycle/release-workstation.ts +216 -0
  376. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  377. package/src/lifecycle/retention-policy.ts +63 -0
  378. package/src/lifecycle/retention.test.ts +363 -0
  379. package/src/lifecycle/retention.ts +508 -0
  380. package/src/lifecycle/run-ledger.ts +7 -1
  381. package/src/op/activities/activity-contracts.ts +1 -0
  382. package/src/op/activities/shell.ts +7 -1
  383. package/src/op/local-executor.ts +27 -4
  384. package/src/op/runtime.ts +42 -0
  385. package/src/plan-summary.test.ts +20 -0
  386. package/src/plan-summary.ts +8 -1
  387. package/src/pr-forge.test.ts +25 -0
  388. package/src/pr-forge.ts +62 -0
  389. package/src/pr-loop.test.ts +13 -1
  390. package/src/pr-loop.ts +41 -4
  391. package/src/pr-report-url.test.ts +131 -0
  392. package/src/pr-report-url.ts +209 -0
  393. package/src/provenance.ts +2 -2
  394. package/src/run-event-store.ts +63 -3
  395. package/src/run-event-stream.ts +8 -4
  396. package/src/run-event.ts +38 -1
  397. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  398. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  399. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  400. package/src/workspace/change-set.schema.json +5 -1
  401. package/src/workspace/check.schema.json +4 -2
  402. package/src/workspace/checks/records.ts +35 -1
  403. package/src/workspace/checks.ts +2 -1
  404. package/src/workspace/ci-green-forge.ts +2 -0
  405. package/src/workspace/ci-green.ts +20 -5
  406. package/src/workspace/ci-run.test.ts +416 -0
  407. package/src/workspace/ci-run.ts +571 -0
  408. package/src/workspace/ci-serve.test.ts +268 -0
  409. package/src/workspace/ci-serve.ts +521 -0
  410. package/src/workspace/declaration.schema.json +64 -0
  411. package/src/workspace/declaration.test.ts +28 -0
  412. package/src/workspace/declaration.ts +38 -0
  413. package/src/workspace/declared-kinds.ts +40 -1
  414. package/src/workspace/delivery-metrics.schema.json +519 -0
  415. package/src/workspace/delivery-metrics.test.ts +236 -0
  416. package/src/workspace/delivery-metrics.ts +286 -0
  417. package/src/workspace/flags-read.ts +121 -0
  418. package/src/workspace/flags.test.ts +141 -0
  419. package/src/workspace/flags.ts +157 -0
  420. package/src/workspace/graph-cli.ts +1 -1
  421. package/src/workspace/intent-cli.ts +46 -5
  422. package/src/workspace/intent-trace.test.ts +173 -0
  423. package/src/workspace/intent-trace.ts +484 -0
  424. package/src/workspace/intent.schema.json +702 -6
  425. package/src/workspace/intent.ts +148 -25
  426. package/src/workspace/older-records.test.ts +412 -0
  427. package/src/workspace/overlap.test.ts +165 -0
  428. package/src/workspace/overlap.ts +340 -0
  429. package/src/workspace/pr-report.schema.json +5 -0
  430. package/src/workspace/read-contract.e2e.test.ts +2 -1
  431. package/src/workspace/reason-codes.test.ts +5 -0
  432. package/src/workspace/reason-codes.ts +3 -0
  433. package/src/workspace/record-assets.test.ts +2 -1
  434. package/src/workspace/records-contract.e2e.test.ts +2 -1
  435. package/src/workspace/records.schema.json +21 -2
  436. package/src/workspace/records.ts +36 -2
  437. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  438. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  439. package/src/workspace/releases.schema.json +123 -1
  440. package/src/workspace/releases.ts +11 -0
  441. package/src/workspace/run-event.schema.json +24 -6
  442. package/src/workspace/status-contract.test.ts +29 -0
  443. package/src/workspace/status-gates.ts +8 -0
  444. package/src/workspace/status-mixed.test.ts +152 -0
  445. package/src/workspace/status-revoked.test.ts +153 -0
  446. package/src/workspace/status-stewards.ts +3 -0
  447. package/src/workspace/status.schema.json +210 -2
  448. package/src/workspace/status.ts +254 -3
  449. package/src/workspace/wip.ts +11 -2
@@ -0,0 +1,464 @@
1
+ /**
2
+ * `chant ci run plan|apply` (#4094, epic #4101): run one pull-request job as
3
+ * a pipeline Op, the first slice of the chant runner.
4
+ *
5
+ * The job is the typed value `prLoopJobs` (../components/pr-pipeline.ts)
6
+ * returns, the one the github, gitlab and forgejo generators wrap in a
7
+ * workflow, so the runner and a generated workflow run the same command with
8
+ * the same values. Nothing reads generated YAML.
9
+ *
10
+ * The job runs as an Op on the local executor (../op/local-executor.ts):
11
+ *
12
+ * - a `setup` phase with the setup lines the default image needs,
13
+ * - a `build` phase with the lines given to run before the command
14
+ * (`--before-script`, which a generated workflow runs in the same place),
15
+ * then the job's build and lint lines with `--pr-build` (#4088),
16
+ * - and a phase named for the job, `plan` or `apply`, with the job's own
17
+ * setup lines and its command in one shell.
18
+ *
19
+ * Each line is one shell step with a clean environment: a short list of the
20
+ * runner's variables (PATH, HOME and the like), the forge's connection and
21
+ * token variables the commands read, any `--pass-env` names, and the job's
22
+ * own variables. The forge values a generated job reads from `${{ ... }}`
23
+ * expressions or GitLab's predefined variables come from flags here:
24
+ * `BASE_SHA` from `--base`, `PR_NUMBER` from `--pr`, and the commit from
25
+ * `--commit`.
26
+ *
27
+ * The steps run in a fresh checkout of the commit: a detached `git worktree`
28
+ * in a temporary directory outside the repository, removed when the run
29
+ * ends. A worktree shares the repository's refs, so what the job records on
30
+ * `chant/lifecycle` (pr-apply's pending gate facts) and the remote it pushes
31
+ * to are the repository's own, as in a forge job's checkout. The report the
32
+ * job writes under `.chant/pr` is copied out of the worktree before it goes,
33
+ * where a forge would keep it as an artifact.
34
+ *
35
+ * The run is recorded as Op `pr-plan` or `pr-apply` in the run ledger
36
+ * (`<env>/runs__<op>.jsonl` on `chant/lifecycle`), and signed when a runner
37
+ * key is given, so `chant ci tick --source runs` (#4044) judges the commit
38
+ * from it. Its events are `kind: "pipeline"` and `runtime: "ci"`, and the
39
+ * record and the events carry `runOrigin: { forge: "chant" }`.
40
+ */
41
+ import { execFileSync } from "node:child_process";
42
+ import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
43
+ import { tmpdir } from "node:os";
44
+ import { dirname, join, relative, resolve, sep } from "node:path";
45
+ import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prLoopJobs, prLoopSetup, PR_LOOP_IMAGE } from "../components/pr-pipeline.js";
46
+ import { detectForge, FORGE_KINDS } from "../pr-forge.js";
47
+ import { formatError, formatWarning } from "../cli/format.js";
48
+ import { pushLifecycleReport } from "../lifecycle/git.js";
49
+ import { ACTIVITY_PROFILES } from "../op/activity-profiles.js";
50
+ import { shellCmd } from "../op/activities/shell.js";
51
+ import { OpRunFailure, runOpLocally } from "../op/local-executor.js";
52
+ import { findWorkspaceRoot } from "../project-root.js";
53
+ import { ciTagSigner, RUNNER_KEY_ENV } from "./ci-green.js";
54
+ import { resolveMemberContext } from "./member-pipeline.js";
55
+ import { signOpRunRecord } from "./trust/op-run-statement.js";
56
+ import { gitTop } from "./tree.js";
57
+ export const CI_JOB_NAMES = ["plan", "apply"];
58
+ /** The Op each job is recorded as, which `ci.green` phases match. */
59
+ export const CI_JOB_OPS = { plan: "pr-plan", apply: "pr-apply" };
60
+ /** Where a job the chant runner ran says it ran (#4094). */
61
+ export const CHANT_RUNNER_ORIGIN = { forge: "chant" };
62
+ /** The runner's variables every job gets, when the runner has them. */
63
+ export const CI_RUN_BASE_ENV = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TZ"];
64
+ /**
65
+ * The forge's connection and token variables the pull-request commands read
66
+ * (`forgeFromEnv` in ../pr-forge.ts), which a forge job gets from the forge
67
+ * and a job here gets from the runner's environment.
68
+ */
69
+ export const CI_RUN_FORGE_ENV = [
70
+ "CHANT_FORGE_TOKEN",
71
+ "GITHUB_TOKEN",
72
+ "GH_TOKEN",
73
+ "GITHUB_REPOSITORY",
74
+ "GITHUB_SERVER_URL",
75
+ "GITHUB_API_URL",
76
+ "GITLAB_TOKEN",
77
+ "CI_API_V4_URL",
78
+ "CI_PROJECT_ID",
79
+ "CI_SERVER_HOST",
80
+ "CI_SERVER_URL",
81
+ ];
82
+ /** How long one step may run: a forge job's own limit, six hours. */
83
+ const STEP_TIMEOUT = "6h";
84
+ export class CiRunError extends Error {
85
+ constructor(message) {
86
+ super(message);
87
+ this.name = "CiRunError";
88
+ }
89
+ }
90
+ /**
91
+ * The job's environment: the runner's base variables, the forge variables
92
+ * and `passEnv` names it has, `CHANT_ENV` as a generated workflow sets it,
93
+ * the commit as the forge spells it, and the job's own variables. A job
94
+ * variable the forge fills from an expression comes from a flag, or from
95
+ * the runner's environment for a token such as `GITHUB_TOKEN`; a literal
96
+ * one, such as `CHANT_RUN_EVENTS_DIR`, is kept as it is.
97
+ */
98
+ export function ciJobEnv(job, input) {
99
+ const out = {};
100
+ for (const name of [...CI_RUN_BASE_ENV, ...CI_RUN_FORGE_ENV, ...(input.passEnv ?? [])]) {
101
+ const value = input.hostEnv[name];
102
+ if (value !== undefined)
103
+ out[name] = value;
104
+ }
105
+ out.CHANT_ENV = input.env;
106
+ out[input.forge === "gitlab" ? "CI_COMMIT_SHA" : "GITHUB_SHA"] = input.values.commit;
107
+ const flags = {
108
+ BASE_SHA: { value: input.values.base, flag: "--base <sha>" },
109
+ PR_NUMBER: { value: input.values.pr !== undefined ? String(input.values.pr) : undefined, flag: "--pr <number>" },
110
+ };
111
+ for (const [name, value] of Object.entries(job.env)) {
112
+ const given = flags[name];
113
+ if (given) {
114
+ if (given.value === undefined)
115
+ throw new CiRunError(`the ${job.jobName} job reads ${name}, which a forge fills from the event; pass ${given.flag}`);
116
+ out[name] = given.value;
117
+ }
118
+ else if (!value.includes("$")) {
119
+ out[name] = value;
120
+ }
121
+ else if (input.hostEnv[name] !== undefined) {
122
+ out[name] = input.hostEnv[name];
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ /** `line` with a `chant` at its start replaced by the command the caller names, when it names one. */
128
+ export function withChant(line, chant) {
129
+ return chant && line.startsWith("chant ") ? `${chant}${line.slice("chant".length)}` : line;
130
+ }
131
+ /** The job's own setup lines and its command, as one shell script, with `chant` at its start replaced by the command the caller names. */
132
+ export function ciJobCommand(job, chant) {
133
+ return [...(job.setup ?? []), withChant(job.command, chant)].join("\n");
134
+ }
135
+ /**
136
+ * The job as an Op: `setup`, `build` and the job's own phase, each line one
137
+ * shell step in `cwd` with the job's environment and nothing else. A phase
138
+ * with no lines is left out.
139
+ */
140
+ export function ciJobOp(input) {
141
+ const step = (cmd) => ({
142
+ kind: "activity",
143
+ fn: "shellCmd",
144
+ args: { cmd, cwd: input.cwd, env: input.jobEnv, inheritEnv: false },
145
+ profile: "atMostOnce",
146
+ timeout: STEP_TIMEOUT,
147
+ });
148
+ const phases = [
149
+ { name: "setup", steps: input.setup.map(step) },
150
+ { name: "build", steps: input.build.map((line) => step(withChant(line, input.chant))) },
151
+ { name: input.job.jobName, steps: [step(ciJobCommand(input.job, input.chant))] },
152
+ ].filter((p) => p.steps.length > 0);
153
+ const op = CI_JOB_OPS[input.job.jobName];
154
+ return {
155
+ name: op,
156
+ overview: input.job.jobName === "plan" ? "Plan the members this pull request reaches" : "Apply the plan a reviewer approved",
157
+ labels: { Env: input.env, Pipeline: "pr", Job: input.job.jobName },
158
+ phases,
159
+ };
160
+ }
161
+ /** The pull-request report statuses that mean the job did not do its work, which a failed job's `failure.code` takes. */
162
+ const FAILED_REPORT_STATUSES = new Set(["plan-failed", "failed", "refused"]);
163
+ /** The exit code a failed shell step's error names (`command exited <n>`), when it names one. */
164
+ function exitCodeOf(error) {
165
+ const m = /command exited (\d+)/.exec(error ?? "");
166
+ return m ? Number(m[1]) : undefined;
167
+ }
168
+ /** The first line of a step's error, for a reason. */
169
+ const firstLine = (text) => text.split("\n").find((l) => l.trim() !== "")?.trim() ?? text;
170
+ /**
171
+ * Why a failed job failed (#4094), from its step records and, when the
172
+ * command wrote one, its pull-request report (`pr-plan.json` or
173
+ * `pr-apply.json` under `reportDir`):
174
+ *
175
+ * - a failed setup or build line: `setup-failed` or `build-failed`, the line
176
+ * and its exit code as the reason, and the phase;
177
+ * - a failed command with a report: the report's `refusal`, else its
178
+ * `status`, with its `message` as the reason, else the members that failed;
179
+ * - a failed command with no report: `command-failed` and the step's error.
180
+ *
181
+ * Undefined when no step failed, such as a run that stopped on an error of
182
+ * its own before any step ran.
183
+ */
184
+ export function ciJobFailure(input) {
185
+ const failed = input.records.find((r) => r.status === "fail");
186
+ if (!failed)
187
+ return undefined;
188
+ const exitCode = exitCodeOf(failed.error);
189
+ const exit = exitCode !== undefined ? { exitCode } : {};
190
+ if (failed.phase === "setup" || failed.phase === "build") {
191
+ const at = input.records.filter((r) => r.phase === failed.phase).indexOf(failed);
192
+ const step = input.op.phases.find((p) => p.name === failed.phase)?.steps[at];
193
+ const line = typeof step?.args?.cmd === "string" ? step.args.cmd : undefined;
194
+ const reason = line !== undefined
195
+ ? `${line} ${exitCode !== undefined ? `exited ${exitCode}` : `failed: ${firstLine(failed.error ?? "")}`}`
196
+ : firstLine(failed.error ?? "the step failed");
197
+ return { code: `${failed.phase}-failed`, reason, phase: failed.phase, ...exit };
198
+ }
199
+ const file = join(input.reportDir, `${CI_JOB_OPS[input.job]}.json`);
200
+ let report;
201
+ try {
202
+ if (existsSync(file))
203
+ report = JSON.parse(readFileSync(file, "utf-8"));
204
+ }
205
+ catch {
206
+ report = undefined;
207
+ }
208
+ const status = typeof report?.status === "string" ? report.status : undefined;
209
+ if (report && status && (FAILED_REPORT_STATUSES.has(status) || typeof report.refusal === "string")) {
210
+ const code = typeof report.refusal === "string" ? report.refusal : status;
211
+ let reason = typeof report.message === "string" && report.message.trim() !== "" ? firstLine(report.message) : undefined;
212
+ if (reason === undefined) {
213
+ const members = Array.isArray(report.members) ? report.members : [];
214
+ const bad = members.filter((m) => m.status === "failed" || m.status === "plan-failed");
215
+ reason = bad.length > 0
216
+ ? `${bad.length === 1 ? "member" : "members"} ${bad.map((m) => `${String(m.member)} ${String(m.status)}${typeof m.error === "string" ? ` (${firstLine(m.error)})` : ""}`).join(", ")}`
217
+ : `the ${CI_JOB_OPS[input.job]} report says ${status}`;
218
+ }
219
+ return { code, reason, phase: failed.phase, ...exit };
220
+ }
221
+ return { code: "command-failed", reason: firstLine(failed.error ?? "the command failed"), phase: failed.phase, ...exit };
222
+ }
223
+ const message = (err) => (err instanceof Error ? err.message : String(err));
224
+ function git(cwd, args) {
225
+ return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
226
+ }
227
+ function tryGit(cwd, args) {
228
+ try {
229
+ return git(cwd, args);
230
+ }
231
+ catch {
232
+ return undefined;
233
+ }
234
+ }
235
+ /** The full commit `rev` names, fetched from `origin` when the repository lacks it. */
236
+ function resolveJobCommit(top, rev) {
237
+ if (rev.startsWith("-"))
238
+ throw new CiRunError(`--commit takes a commit, not ${JSON.stringify(rev)}`);
239
+ const local = tryGit(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`]);
240
+ if (local)
241
+ return local;
242
+ if (tryGit(top, ["fetch", "--quiet", "--no-tags", "origin", rev]) !== undefined) {
243
+ const fetched = tryGit(top, ["rev-parse", "--verify", "--quiet", "FETCH_HEAD^{commit}"]);
244
+ if (fetched && (fetched === rev || fetched.startsWith(rev)))
245
+ return fetched;
246
+ }
247
+ throw new CiRunError(`${rev} names no commit here or on origin`);
248
+ }
249
+ /** The member a job runs for, and its directory from the repository root, as the generator decides it. */
250
+ export function ciJobPlace(cwd, top) {
251
+ const found = findWorkspaceRoot(cwd);
252
+ const member = found ? resolveMemberContext(cwd, found) : undefined;
253
+ const at = member ? member.memberRoot : cwd;
254
+ const dir = relative(top, realpathSync(at)).split(sep).join("/");
255
+ return { ...(member ? { member: member.member.name } : {}), dir: dir === "" ? "." : dir };
256
+ }
257
+ /** A detached worktree of `commit` in a new temporary directory, and how to remove it. */
258
+ function freshCheckout(top, commit) {
259
+ const holder = mkdtempSync(join(realpathSync(tmpdir()), "chant-ci-run-"));
260
+ const dir = join(holder, "checkout");
261
+ try {
262
+ git(top, ["worktree", "add", "--quiet", "--detach", dir, commit]);
263
+ }
264
+ catch (err) {
265
+ rmSync(holder, { recursive: true, force: true });
266
+ const stderr = err.stderr;
267
+ throw new CiRunError(`could not check out ${commit.slice(0, 12)} in a fresh worktree: ${String(stderr ?? err).trim()}`);
268
+ }
269
+ return {
270
+ dir,
271
+ remove() {
272
+ tryGit(top, ["worktree", "remove", "--force", dir]);
273
+ rmSync(holder, { recursive: true, force: true });
274
+ tryGit(top, ["worktree", "prune"]);
275
+ },
276
+ };
277
+ }
278
+ const ACTIVITIES = new Map([["shellCmd", shellCmd]]);
279
+ /**
280
+ * Run one pull-request job as a pipeline Op in a fresh checkout of the
281
+ * commit, record it, and copy its report out. Throws {@link CiRunError}
282
+ * before anything runs when the request can't be run.
283
+ */
284
+ export async function ciRun(req) {
285
+ const hostEnv = req.hostEnv ?? process.env;
286
+ const cwd = realpathSync(resolve(req.cwd));
287
+ const top = gitTop(cwd);
288
+ if (!top)
289
+ throw new CiRunError("a job runs in a fresh checkout of a commit, and this directory is not in a git repository");
290
+ const commit = resolveJobCommit(top, req.commit ?? "HEAD");
291
+ const place = ciJobPlace(cwd, top);
292
+ const jobs = prLoopJobs(req.forge, req.env, req.loop ?? {}, place.member);
293
+ const job = jobs.find((j) => j.jobName === req.job);
294
+ if (!job)
295
+ throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
296
+ const jobEnv = ciJobEnv(job, { forge: req.forge, env: req.env, values: { base: req.base, pr: req.pr, commit }, hostEnv, passEnv: req.passEnv });
297
+ // 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.
298
+ let signer;
299
+ const early = [];
300
+ if (req.runnerKey !== undefined) {
301
+ try {
302
+ signer = ciTagSigner(top, commit, req.runnerKey, "the runs it signed");
303
+ }
304
+ catch (err) {
305
+ if (!req.runnerKeyFromEnv)
306
+ throw new CiRunError(`the run would not be signed: ${message(err)}`);
307
+ early.push(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
308
+ }
309
+ }
310
+ const checkout = freshCheckout(top, commit);
311
+ try {
312
+ const jobDir = place.dir === "." ? checkout.dir : join(checkout.dir, place.dir);
313
+ const op = ciJobOp({
314
+ job,
315
+ env: req.env,
316
+ cwd: jobDir,
317
+ jobEnv,
318
+ setup: prLoopSetup(req.image ?? PR_LOOP_IMAGE),
319
+ build: [...(req.beforeScript ?? []), ...(job.build ?? [])],
320
+ ...(req.chant ? { chant: req.chant } : {}),
321
+ });
322
+ const runId = req.runId ?? `chant-${Date.now()}`;
323
+ const warnings = [...early];
324
+ const resume = req.job === "apply" && req.resumeRecord ? { host: resolve(req.resumeRecord), job: join(jobDir, PR_APPLY_RECORD) } : undefined;
325
+ if (resume && existsSync(resume.host)) {
326
+ mkdirSync(dirname(resume.job), { recursive: true });
327
+ copyFileSync(resume.host, resume.job);
328
+ }
329
+ let ledgerError;
330
+ let result;
331
+ try {
332
+ result = await runOpLocally(op, ACTIVITIES, ACTIVITY_PROFILES, req.signal, {
333
+ runId,
334
+ env: req.env,
335
+ cwd: jobDir,
336
+ commit,
337
+ ledger: { cwd: jobDir },
338
+ runner: { kind: "pipeline", runtime: "ci", runOrigin: CHANT_RUNNER_ORIGIN },
339
+ describeFailure: (records) => ciJobFailure({ job: req.job, op, records, reportDir: join(jobDir, PR_LOOP_REPORT_DIR) }),
340
+ ...(req.runEvents ? { runEvents: { write: req.runEvents } } : {}),
341
+ ...(req.stepOutput ? { stepOutput: req.stepOutput } : {}),
342
+ ...(signer ? { signRecord: (record) => signOpRunRecord(record, signer) } : {}),
343
+ onSignError: (err) => warnings.push(`the run was not signed: ${message(err)}`),
344
+ onLedgerError: (err) => {
345
+ ledgerError = `the job ran, and its record could not be written to chant/lifecycle: ${message(err)}`;
346
+ },
347
+ });
348
+ }
349
+ catch (err) {
350
+ if (!(err instanceof OpRunFailure))
351
+ throw err;
352
+ result = err.result;
353
+ }
354
+ if (resume && existsSync(resume.job)) {
355
+ try {
356
+ mkdirSync(dirname(resume.host), { recursive: true });
357
+ copyFileSync(resume.job, resume.host);
358
+ }
359
+ catch (err) {
360
+ warnings.push(`the resume record could not be kept at ${resume.host}: ${message(err)}`);
361
+ }
362
+ }
363
+ const written = join(jobDir, PR_LOOP_REPORT_DIR);
364
+ let report = null;
365
+ if (existsSync(written)) {
366
+ report = resolve(cwd, req.output ?? PR_LOOP_REPORT_DIR);
367
+ cpSync(written, report, { recursive: true });
368
+ }
369
+ if (ledgerError)
370
+ throw new CiRunError(ledgerError);
371
+ const pushed = req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
372
+ const failed = result.records.find((r) => r.status === "fail");
373
+ return {
374
+ op: op.name,
375
+ status: result.status,
376
+ record: result.record,
377
+ ...(failed ? { failure: { phase: failed.phase, error: failed.error ?? "the step failed" } } : {}),
378
+ report,
379
+ signer: signer?.principal ?? null,
380
+ pushed: pushed.pushed,
381
+ ...(pushed.pushWarning ? { pushWarning: pushed.pushWarning } : {}),
382
+ warnings,
383
+ checkout: checkout.dir,
384
+ };
385
+ }
386
+ finally {
387
+ checkout.remove();
388
+ }
389
+ }
390
+ /** The text `chant ci run` prints when the run settles. */
391
+ export function formatCiRun(r) {
392
+ const lines = [`${r.op} ${r.status === "ok" ? "passed" : r.status === "fail" ? "failed" : r.status} at ${r.record.gitSha?.slice(0, 12) ?? "an unknown commit"}, run ${r.record.id} in ${r.record.env}`];
393
+ const why = r.record.failure;
394
+ if (why)
395
+ lines.push(` failure: ${why.code}${why.phase ? ` in ${why.phase}` : ""}${why.exitCode !== undefined ? `, exit ${why.exitCode}` : ""}: ${why.reason}`);
396
+ else if (r.failure)
397
+ lines.push(` ${r.failure.phase}: ${r.failure.error}`);
398
+ 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"}`);
399
+ if (r.report)
400
+ lines.push(` report: ${r.report}`);
401
+ if (!r.pushed)
402
+ lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
403
+ return lines.join("\n");
404
+ }
405
+ // ── The command ──────────────────────────────────────────────────────────────
406
+ const USAGE = "chant ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
407
+ "[--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--image <image>] [--before-script <line>]... " +
408
+ "[--pr-build] [--pr-build-command <line>] " +
409
+ "[--chant <command>] [--pass-env <NAME>]... [--key <runner key.pem>] [-o <dir>]";
410
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
411
+ /** `chant ci run plan|apply` (#4094). */
412
+ export async function runCiRun(ctx) {
413
+ const { args } = ctx;
414
+ try {
415
+ const job = args.extraPositional;
416
+ if (job !== "plan" && job !== "apply")
417
+ throw new CiRunError(`ci run takes the job to run, plan or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
418
+ if (!args.env)
419
+ throw new CiRunError("--env names the environment the pipeline is for, as chant build --components --generate <lexicon> --pr-loop took it: --env <env>");
420
+ const forge = args.forge ?? detectForge(process.env);
421
+ if (!forge)
422
+ throw new CiRunError("--forge names the forge the job posts to: --forge github|gitlab|forgejo");
423
+ if (!FORGE_KINDS.includes(forge))
424
+ throw new CiRunError(`--forge takes github, gitlab or forgejo, not ${JSON.stringify(forge)}`);
425
+ for (const name of args.passEnv ?? [])
426
+ if (!ENV_NAME.test(name))
427
+ throw new CiRunError(`--pass-env takes a variable's name, not ${JSON.stringify(name)}`);
428
+ const fromFlag = args.key !== undefined;
429
+ const runnerKey = fromFlag ? readFileSync(resolve(args.key), "utf-8") : process.env[RUNNER_KEY_ENV] || undefined;
430
+ const result = await ciRun({
431
+ cwd: process.cwd(),
432
+ job,
433
+ env: args.env,
434
+ forge: forge,
435
+ ...(args.base ? { base: args.base } : {}),
436
+ ...(args.pr !== undefined ? { pr: args.pr } : {}),
437
+ ...(args.commit ? { commit: args.commit } : {}),
438
+ loop: {
439
+ ...(args.gate ? { gate: args.gate } : {}),
440
+ ...(args.branch ? { branch: args.branch } : {}),
441
+ ...(args.eventsJson ? { events: true } : {}),
442
+ ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
443
+ ...(args.prBuild ? { build: true } : {}),
444
+ ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
445
+ },
446
+ ...(args.image ? { image: args.image } : {}),
447
+ ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
448
+ ...(args.ciChant ? { chant: args.ciChant } : {}),
449
+ ...(args.passEnv?.length ? { passEnv: args.passEnv } : {}),
450
+ ...(args.output ? { output: args.output } : {}),
451
+ ...(runnerKey !== undefined ? { runnerKey, runnerKeyFromEnv: !fromFlag } : {}),
452
+ ...(args.eventsJson ? { runEvents: (line) => void process.stdout.write(line) } : {}),
453
+ stepOutput: (line) => void process.stderr.write(`${line}\n`),
454
+ });
455
+ for (const w of result.warnings)
456
+ console.error(formatWarning({ message: w }));
457
+ console.error(formatCiRun(result));
458
+ return result.status === "ok" ? 0 : 1;
459
+ }
460
+ catch (err) {
461
+ console.error(formatError({ message: `chant ci run: ${message(err)}`, hint: USAGE }));
462
+ return 1;
463
+ }
464
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * `chant ci serve` (#4095, epic #4101): the chant runner's trigger loop on one
3
+ * host. It polls the forge for the open pull requests into the target branch
4
+ * and for the branch's head, and runs the pull-request jobs with
5
+ * {@link ciRun} (./ci-run.ts): `plan` for each new pull request head, and
6
+ * `apply` for each new commit on the target branch, measured from the head
7
+ * the previous poll saw (the push's `before`).
8
+ *
9
+ * - Dedupe. A state file in the state directory keeps every job it started,
10
+ * keyed by job and commit (`plan:<pr>:<sha>`, `apply:<sha>`), with how it
11
+ * ended, and the target head the last apply was measured to. A job already
12
+ * in it is not started again, unless it failed and `rerunFailed` asks for
13
+ * it: the forge's "re-run failed jobs".
14
+ * - One apply per group. An apply takes the git-ref lease (../lifecycle/lease.ts,
15
+ * the one `chant operator` holds) named for `prApplyGroup`, renews it while
16
+ * it runs and releases it after. An apply that finds it held waits for a
17
+ * later poll, as a forge's concurrency group queues it.
18
+ * - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
19
+ * as its `--resume` record, so a re-run of a failed apply at the same
20
+ * commit finishes under the approval it has. This replaces the forge's CI
21
+ * cache.
22
+ * - Capacity. At most `maxJobs` jobs run at once. A job over the limit is not
23
+ * recorded, so a later poll starts it.
24
+ *
25
+ * Host mode only: each job runs in a fresh worktree on this host as this
26
+ * user, with the clean environment `ciRun` gives it. A pull request from a
27
+ * fork is never planned, and with `authors` only those people's pull requests
28
+ * are. Container isolation is #4015.
29
+ *
30
+ * A webhook is not here yet. {@link ciServeSchedule} takes triggers, and
31
+ * {@link ciServeObserve} is the poll that makes them; a webhook handler would
32
+ * turn a checked payload into the same triggers and schedule them.
33
+ */
34
+ import { type OpenPullRequest } from "../pr-forge.js";
35
+ import type { CommandContext } from "../cli/registry.js";
36
+ import { type CiJobName, type CiRunRequest, type CiRunResult } from "./ci-run.js";
37
+ /** The forges `ci serve` polls. GitLab is later. */
38
+ export type CiServeForgeKind = "github" | "forgejo";
39
+ /** What `ci serve` reads from the forge: the two calls GitHub's and Forgejo's clients have (../pr-forge.ts). */
40
+ export interface CiServeForge {
41
+ openPullRequests(base: string): Promise<OpenPullRequest[]>;
42
+ branchHead(branch: string): Promise<string | undefined>;
43
+ }
44
+ /** A job the forge's state calls for: the plan of a pull request's head, or the apply of a target-branch commit. */
45
+ export type CiServeTrigger = {
46
+ job: "plan";
47
+ commit: string;
48
+ base: string;
49
+ pr: number;
50
+ rerun?: boolean;
51
+ } | {
52
+ job: "apply";
53
+ commit: string;
54
+ base: string;
55
+ rerun?: boolean;
56
+ };
57
+ /** One job in the state file. */
58
+ export interface CiServeJobState {
59
+ job: CiJobName;
60
+ commit: string;
61
+ base: string;
62
+ pr?: number;
63
+ /** `running` while it runs; a `running` entry no live process holds is taken as failed. */
64
+ status: "running" | "ok" | "fail";
65
+ runId: string;
66
+ attempts: number;
67
+ startedAt: string;
68
+ settledAt?: string;
69
+ /** Why it failed, in a line. */
70
+ failure?: string;
71
+ /** Where its report was copied. */
72
+ report?: string;
73
+ }
74
+ /** The state file, `<state>/<prApplyGroup>.json`. */
75
+ export interface CiServeState {
76
+ schema: 1;
77
+ forge: CiServeForgeKind;
78
+ repo: string;
79
+ env: string;
80
+ branch: string;
81
+ member?: string;
82
+ /** The target branch's head the last apply was measured to, or the head the first poll found. */
83
+ target?: string;
84
+ jobs: Record<string, CiServeJobState>;
85
+ }
86
+ /** What a pass did, in order, for the log. */
87
+ export type CiServeEvent = {
88
+ type: "baseline";
89
+ commit: string;
90
+ } | {
91
+ type: "no-branch";
92
+ branch: string;
93
+ } | {
94
+ type: "skipped";
95
+ pr: number;
96
+ reason: "fork" | "author";
97
+ author?: string;
98
+ } | {
99
+ type: "held";
100
+ job: CiJobName;
101
+ commit: string;
102
+ pr?: number;
103
+ reason: "capacity" | "lease";
104
+ heldBy?: string;
105
+ } | {
106
+ type: "started";
107
+ job: CiJobName;
108
+ commit: string;
109
+ pr?: number;
110
+ base: string;
111
+ runId: string;
112
+ attempt: number;
113
+ resume?: string;
114
+ } | {
115
+ type: "settled";
116
+ job: CiJobName;
117
+ commit: string;
118
+ pr?: number;
119
+ status: "ok" | "fail";
120
+ failure?: string;
121
+ report?: string;
122
+ } | {
123
+ type: "error";
124
+ message: string;
125
+ };
126
+ /** The `ciRun` request fields `ci serve` passes through to every job: the generator's options and the runner's. */
127
+ export type CiServeJobOptions = Omit<CiRunRequest, "cwd" | "job" | "env" | "forge" | "base" | "pr" | "commit" | "output" | "resumeRecord" | "runId">;
128
+ export interface CiServeOptions {
129
+ /** The host's clone, or a workspace member's directory in it. */
130
+ cwd: string;
131
+ forge: CiServeForgeKind;
132
+ /** `owner/name`. */
133
+ repo: string;
134
+ env: string;
135
+ client: CiServeForge;
136
+ /** Default `<git common dir>/chant/ci-serve`. */
137
+ stateDir?: string;
138
+ /** Default 1. */
139
+ maxJobs?: number;
140
+ /** Plan only pull requests these logins opened, without case. Default anyone with a branch in the repository. */
141
+ authors?: readonly string[];
142
+ /** Run again the failed apply at the target's head, and the failed plans of open heads. */
143
+ rerunFailed?: boolean;
144
+ /** Passed to each job. `job.hostEnv` gets `GITHUB_REPOSITORY` set to `repo`. */
145
+ job?: CiServeJobOptions;
146
+ /** This process's lease identity. Default `currentHolderId()`. */
147
+ holder?: string;
148
+ leaseTtlMs?: number;
149
+ log?: (event: CiServeEvent) => void;
150
+ now?: () => Date;
151
+ /** Runs a job. Default {@link ciRun}. */
152
+ run?: (req: CiRunRequest) => Promise<CiRunResult>;
153
+ }
154
+ /** The jobs this process runs now, by state key, shared by every pass of one loop. */
155
+ export interface CiServeRuntime {
156
+ inflight: Map<string, Promise<CiServeJobState>>;
157
+ }
158
+ export declare const newCiServeRuntime: () => CiServeRuntime;
159
+ /** The state key of a job. */
160
+ export declare function ciServeKey(t: {
161
+ job: CiJobName;
162
+ commit: string;
163
+ pr?: number;
164
+ }): string;
165
+ /** Where a serve keeps its state when `--state` names nowhere: beside the run events, in the repository's common git directory. */
166
+ export declare function defaultCiServeStateDir(cwd: string): string;
167
+ /** The state file, or a new one. */
168
+ export declare function readCiServeState(file: string, fresh: Omit<CiServeState, "schema" | "jobs">): CiServeState;
169
+ /**
170
+ * One poll of the forge, as triggers: an apply when the target's head moved
171
+ * since the last apply (none on the first poll, which only notes the head),
172
+ * then a plan for each open pull request head not yet planned, oldest pull
173
+ * request first. A job already in the state file is left out unless it
174
+ * failed and `rerunFailed` is set. The first poll's head is written to the
175
+ * state file.
176
+ */
177
+ export declare function ciServeObserve(opts: CiServeOptions, rt?: CiServeRuntime): Promise<{
178
+ triggers: CiServeTrigger[];
179
+ events: CiServeEvent[];
180
+ }>;
181
+ /** What {@link ciServeSchedule} did with the triggers. */
182
+ export interface CiServeScheduled {
183
+ /** Each job started, settling to its state entry. */
184
+ started: Promise<CiServeJobState>[];
185
+ events: CiServeEvent[];
186
+ }
187
+ /**
188
+ * Start the triggers' jobs, at most `maxJobs` at once with the jobs this
189
+ * process already runs. An apply first takes its group's lease. A trigger
190
+ * that can't start (over capacity, lease held, or already running here) is
191
+ * reported and not recorded, so the next poll finds it again.
192
+ */
193
+ export declare function ciServeSchedule(opts: CiServeOptions, triggers: readonly CiServeTrigger[], rt?: CiServeRuntime): Promise<CiServeScheduled>;
194
+ /** One pass: observe, schedule, and with `wait` wait for the jobs it started. */
195
+ export declare function ciServePass(opts: CiServeOptions, rt?: CiServeRuntime, wait?: boolean): Promise<{
196
+ events: CiServeEvent[];
197
+ jobs: CiServeJobState[];
198
+ }>;
199
+ /**
200
+ * The loop: a pass every `intervalMs` until `signal` aborts, jobs running
201
+ * across passes up to `maxJobs`. A pass the forge fails is logged and the
202
+ * loop goes on. On abort it waits for the jobs that run.
203
+ */
204
+ export declare function ciServeLoop(opts: CiServeOptions & {
205
+ intervalMs: number;
206
+ signal?: AbortSignal;
207
+ }): Promise<void>;
208
+ /** One line for an event, as `ci serve` prints it. */
209
+ export declare function formatCiServeEvent(e: CiServeEvent): string;
210
+ /** `--interval`: seconds, or a duration such as `30s` or `2m`. Default 30 seconds. */
211
+ export declare function ciServeInterval(value: string | undefined): number;
212
+ /** `chant ci serve` (#4095). */
213
+ export declare function runCiServe(ctx: CommandContext): Promise<number>;
214
+ //# sourceMappingURL=ci-serve.d.ts.map