@intentius/chant 0.124.32 → 0.124.34

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 (320) hide show
  1. package/dist/ci-jobs.d.ts +4 -0
  2. package/dist/ci-jobs.d.ts.map +1 -1
  3. package/dist/ci-jobs.js +9 -2
  4. package/dist/cli/handlers/build.d.ts.map +1 -1
  5. package/dist/cli/handlers/build.js +18 -3
  6. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  7. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  8. package/dist/cli/handlers/lifecycle-health.js +150 -0
  9. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  10. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  11. package/dist/cli/handlers/lifecycle.js +1 -1
  12. package/dist/cli/handlers/pr.d.ts +2 -0
  13. package/dist/cli/handlers/pr.d.ts.map +1 -1
  14. package/dist/cli/handlers/pr.js +124 -2
  15. package/dist/cli/handlers/promote.d.ts.map +1 -1
  16. package/dist/cli/handlers/promote.js +277 -74
  17. package/dist/cli/handlers/release.d.ts +16 -1
  18. package/dist/cli/handlers/release.d.ts.map +1 -1
  19. package/dist/cli/handlers/release.js +83 -11
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/main.js +99 -22
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +30 -1
  24. package/dist/cli/registry.d.ts +21 -5
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/components/deploy-inputs.d.ts +91 -0
  27. package/dist/components/deploy-inputs.d.ts.map +1 -0
  28. package/dist/components/deploy-inputs.js +184 -0
  29. package/dist/components/driver.d.ts.map +1 -1
  30. package/dist/components/driver.js +9 -0
  31. package/dist/components/gate-plan.d.ts +8 -0
  32. package/dist/components/gate-plan.d.ts.map +1 -1
  33. package/dist/components/gate-plan.js +6 -0
  34. package/dist/components/pr-pipeline.d.ts +33 -10
  35. package/dist/components/pr-pipeline.d.ts.map +1 -1
  36. package/dist/components/pr-pipeline.js +42 -11
  37. package/dist/components/pr-run.d.ts +1 -1
  38. package/dist/components/pr-run.d.ts.map +1 -1
  39. package/dist/components/promote-rounds.d.ts +102 -0
  40. package/dist/components/promote-rounds.d.ts.map +1 -0
  41. package/dist/components/promote-rounds.js +174 -0
  42. package/dist/components/release-compile.d.ts +37 -2
  43. package/dist/components/release-compile.d.ts.map +1 -1
  44. package/dist/components/release-compile.js +64 -8
  45. package/dist/components/release-gate.d.ts +30 -0
  46. package/dist/components/release-gate.d.ts.map +1 -1
  47. package/dist/components/release-gate.js +92 -42
  48. package/dist/components/release-promote.d.ts +17 -3
  49. package/dist/components/release-promote.d.ts.map +1 -1
  50. package/dist/components/release-promote.js +79 -18
  51. package/dist/components/verbs/bom-writer.d.ts +1 -1
  52. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  53. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  54. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  56. package/dist/components/wave-plan.d.ts +10 -0
  57. package/dist/components/wave-plan.d.ts.map +1 -1
  58. package/dist/components/wave-plan.js +32 -4
  59. package/dist/graph-declared.d.ts +4 -0
  60. package/dist/graph-declared.d.ts.map +1 -1
  61. package/dist/graph-declared.js +11 -1
  62. package/dist/graph-detail.d.ts.map +1 -1
  63. package/dist/graph-detail.js +32 -7
  64. package/dist/health.d.ts +110 -0
  65. package/dist/health.d.ts.map +1 -0
  66. package/dist/health.js +105 -0
  67. package/dist/health.schema.json +191 -0
  68. package/dist/lexicon.d.ts +27 -0
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/merge-record.d.ts +104 -0
  71. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  72. package/dist/lifecycle/merge-record.js +102 -0
  73. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  74. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  75. package/dist/lifecycle/publish-attachments.js +27 -13
  76. package/dist/lifecycle/publish-npm.d.ts +3 -2
  77. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  78. package/dist/lifecycle/publish-npm.js +4 -3
  79. package/dist/lifecycle/release-at.d.ts +40 -0
  80. package/dist/lifecycle/release-at.d.ts.map +1 -0
  81. package/dist/lifecycle/release-at.js +83 -0
  82. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  83. package/dist/lifecycle/release-cut.js +23 -12
  84. package/dist/lifecycle/release-publish.d.ts +28 -12
  85. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  86. package/dist/lifecycle/release-publish.js +29 -17
  87. package/dist/lifecycle/release-store.d.ts +25 -2
  88. package/dist/lifecycle/release-store.d.ts.map +1 -1
  89. package/dist/lifecycle/release-store.js +40 -13
  90. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  91. package/dist/lifecycle/run-ledger.js +1 -0
  92. package/dist/lint/rules/op/index.d.ts +2 -1
  93. package/dist/lint/rules/op/index.d.ts.map +1 -1
  94. package/dist/lint/rules/op/index.js +4 -2
  95. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  96. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  97. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  98. package/dist/op/activities/activity-contracts.d.ts +5 -0
  99. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  100. package/dist/op/activities/activity-contracts.js +3 -0
  101. package/dist/op/activities/shell.d.ts +38 -0
  102. package/dist/op/activities/shell.d.ts.map +1 -1
  103. package/dist/op/activities/shell.js +89 -3
  104. package/dist/op/builders.d.ts +7 -0
  105. package/dist/op/builders.d.ts.map +1 -1
  106. package/dist/op/builders.js +7 -0
  107. package/dist/op/runtime.d.ts +2 -0
  108. package/dist/op/runtime.d.ts.map +1 -1
  109. package/dist/pr-loop.d.ts +14 -3
  110. package/dist/pr-loop.d.ts.map +1 -1
  111. package/dist/pr-loop.js +15 -5
  112. package/dist/pr-report-url.d.ts +2 -2
  113. package/dist/pr-report-url.d.ts.map +1 -1
  114. package/dist/runner-start.d.ts +179 -0
  115. package/dist/runner-start.d.ts.map +1 -0
  116. package/dist/runner-start.js +277 -0
  117. package/dist/wave-runtime.mjs +236 -229
  118. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  119. package/dist/workspace/__fixtures__/contract-repo.js +13 -2
  120. package/dist/workspace/check.schema.json +88 -2
  121. package/dist/workspace/checks/ci-workflow.d.ts +17 -0
  122. package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
  123. package/dist/workspace/checks/ci-workflow.js +123 -0
  124. package/dist/workspace/checks/security.d.ts +50 -0
  125. package/dist/workspace/checks/security.d.ts.map +1 -0
  126. package/dist/workspace/checks/security.js +305 -0
  127. package/dist/workspace/checks.d.ts +2 -0
  128. package/dist/workspace/checks.d.ts.map +1 -1
  129. package/dist/workspace/checks.js +8 -0
  130. package/dist/workspace/ci-builds.d.ts +163 -0
  131. package/dist/workspace/ci-builds.d.ts.map +1 -0
  132. package/dist/workspace/ci-builds.js +315 -0
  133. package/dist/workspace/ci-green-cli.d.ts +2 -1
  134. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  135. package/dist/workspace/ci-green-cli.js +25 -5
  136. package/dist/workspace/ci-green-forge.d.ts +12 -0
  137. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  138. package/dist/workspace/ci-green-forge.js +12 -0
  139. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  140. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  141. package/dist/workspace/ci-green-workflow.js +128 -1
  142. package/dist/workspace/ci-green.d.ts +89 -4
  143. package/dist/workspace/ci-green.d.ts.map +1 -1
  144. package/dist/workspace/ci-green.js +310 -23
  145. package/dist/workspace/ci-init.d.ts +100 -0
  146. package/dist/workspace/ci-init.d.ts.map +1 -0
  147. package/dist/workspace/ci-init.js +434 -0
  148. package/dist/workspace/ci-last-green.schema.json +49 -0
  149. package/dist/workspace/ci-list.schema.json +35 -1
  150. package/dist/workspace/ci-run-index.d.ts +1 -1
  151. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  152. package/dist/workspace/ci-run-index.schema.json +1 -1
  153. package/dist/workspace/ci-run.d.ts +12 -6
  154. package/dist/workspace/ci-run.d.ts.map +1 -1
  155. package/dist/workspace/ci-run.js +72 -16
  156. package/dist/workspace/ci-runner-start.d.ts +77 -0
  157. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  158. package/dist/workspace/ci-runner-start.js +185 -0
  159. package/dist/workspace/ci-serve.d.ts +76 -7
  160. package/dist/workspace/ci-serve.d.ts.map +1 -1
  161. package/dist/workspace/ci-serve.js +153 -19
  162. package/dist/workspace/ci-workflow.d.ts +91 -0
  163. package/dist/workspace/ci-workflow.d.ts.map +1 -0
  164. package/dist/workspace/ci-workflow.js +33 -0
  165. package/dist/workspace/declaration.d.ts +35 -0
  166. package/dist/workspace/declaration.d.ts.map +1 -1
  167. package/dist/workspace/declaration.js +12 -2
  168. package/dist/workspace/declaration.schema.json +51 -0
  169. package/dist/workspace/init.js +2 -2
  170. package/dist/workspace/ls.d.ts +2 -0
  171. package/dist/workspace/ls.d.ts.map +1 -1
  172. package/dist/workspace/ls.js +1 -1
  173. package/dist/workspace/ls.schema.json +2 -1
  174. package/dist/workspace/member-pipeline.d.ts +1 -0
  175. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  176. package/dist/workspace/member-pipeline.js +2 -0
  177. package/dist/workspace/merge-record.schema.json +49 -0
  178. package/dist/workspace/pr-report.schema.json +3 -3
  179. package/dist/workspace/reason-codes.d.ts +26 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/reason-codes.js +28 -0
  182. package/dist/workspace/release-candidates.d.ts +129 -0
  183. package/dist/workspace/release-candidates.d.ts.map +1 -0
  184. package/dist/workspace/release-candidates.js +233 -0
  185. package/dist/workspace/release-candidates.schema.json +353 -0
  186. package/dist/workspace/release-workflow.d.ts +31 -0
  187. package/dist/workspace/release-workflow.d.ts.map +1 -0
  188. package/dist/workspace/release-workflow.js +150 -0
  189. package/dist/workspace/security.d.ts +40 -0
  190. package/dist/workspace/security.d.ts.map +1 -0
  191. package/dist/workspace/security.js +99 -0
  192. package/package.json +8 -1
  193. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  194. package/src/ci-jobs.ts +11 -2
  195. package/src/cli/handlers/build.test.ts +36 -0
  196. package/src/cli/handlers/build.ts +18 -3
  197. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  198. package/src/cli/handlers/lifecycle-health.ts +157 -0
  199. package/src/cli/handlers/lifecycle.ts +1 -1
  200. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  201. package/src/cli/handlers/pr-record.test.ts +201 -0
  202. package/src/cli/handlers/pr.test.ts +22 -0
  203. package/src/cli/handlers/pr.ts +127 -4
  204. package/src/cli/handlers/promote.test.ts +17 -0
  205. package/src/cli/handlers/promote.ts +296 -84
  206. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  207. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  208. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  209. package/src/cli/handlers/release.e2e.test.ts +19 -28
  210. package/src/cli/handlers/release.ts +81 -11
  211. package/src/cli/main.ts +99 -22
  212. package/src/cli/parse-args.ts +22 -1
  213. package/src/cli/registry.ts +21 -5
  214. package/src/components/deploy-inputs.test.ts +108 -0
  215. package/src/components/deploy-inputs.ts +225 -0
  216. package/src/components/driver.ts +11 -0
  217. package/src/components/gate-plan.ts +9 -0
  218. package/src/components/pr-pipeline.ts +56 -15
  219. package/src/components/pr-run.ts +1 -1
  220. package/src/components/promote-rounds.test.ts +220 -0
  221. package/src/components/promote-rounds.ts +232 -0
  222. package/src/components/release-compile.test.ts +34 -0
  223. package/src/components/release-compile.ts +89 -8
  224. package/src/components/release-gate.test.ts +47 -0
  225. package/src/components/release-gate.ts +111 -46
  226. package/src/components/release-promote.test.ts +55 -2
  227. package/src/components/release-promote.ts +93 -23
  228. package/src/components/verbs/bom-writer.ts +1 -1
  229. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  230. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  231. package/src/components/wave-plan.ts +32 -4
  232. package/src/graph-declared-exports.test.ts +40 -0
  233. package/src/graph-declared.ts +9 -1
  234. package/src/graph-detail.test.ts +57 -0
  235. package/src/graph-detail.ts +33 -7
  236. package/src/health.schema.json +191 -0
  237. package/src/health.test.ts +62 -0
  238. package/src/health.ts +178 -0
  239. package/src/lexicon.ts +41 -1
  240. package/src/lifecycle/merge-record.test.ts +111 -0
  241. package/src/lifecycle/merge-record.ts +154 -0
  242. package/src/lifecycle/publish-attachments.ts +25 -13
  243. package/src/lifecycle/publish-npm.ts +4 -3
  244. package/src/lifecycle/publish-targets.test.ts +34 -4
  245. package/src/lifecycle/release-at.test.ts +48 -0
  246. package/src/lifecycle/release-at.ts +100 -0
  247. package/src/lifecycle/release-cut.test.ts +41 -0
  248. package/src/lifecycle/release-cut.ts +22 -11
  249. package/src/lifecycle/release-publish.test.ts +43 -13
  250. package/src/lifecycle/release-publish.ts +38 -18
  251. package/src/lifecycle/release-store.ts +57 -13
  252. package/src/lifecycle/run-ledger.test.ts +13 -0
  253. package/src/lifecycle/run-ledger.ts +1 -0
  254. package/src/lint/rules/op/index.ts +4 -2
  255. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  256. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  257. package/src/op/activities/activity-contracts.ts +3 -0
  258. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  259. package/src/op/activities/shell.test.ts +44 -1
  260. package/src/op/activities/shell.ts +110 -3
  261. package/src/op/builders.ts +7 -0
  262. package/src/op/runtime.ts +2 -0
  263. package/src/op/step-console.test.ts +4 -2
  264. package/src/pr-loop.test.ts +29 -0
  265. package/src/pr-loop.ts +26 -7
  266. package/src/pr-report-url.ts +2 -2
  267. package/src/runner-start-bundle.test.ts +29 -0
  268. package/src/runner-start.test.ts +179 -0
  269. package/src/runner-start.ts +329 -0
  270. package/src/workspace/__fixtures__/contract-repo.ts +11 -2
  271. package/src/workspace/check-contract.test.ts +4 -2
  272. package/src/workspace/check.schema.json +88 -2
  273. package/src/workspace/checks/ci-workflow.test.ts +150 -0
  274. package/src/workspace/checks/ci-workflow.ts +129 -0
  275. package/src/workspace/checks/security.test.ts +138 -0
  276. package/src/workspace/checks/security.ts +305 -0
  277. package/src/workspace/checks.ts +8 -0
  278. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  279. package/src/workspace/ci-builds.test.ts +174 -0
  280. package/src/workspace/ci-builds.ts +385 -0
  281. package/src/workspace/ci-green-builds.test.ts +180 -0
  282. package/src/workspace/ci-green-cli.test.ts +54 -0
  283. package/src/workspace/ci-green-cli.ts +26 -5
  284. package/src/workspace/ci-green-forge.ts +24 -0
  285. package/src/workspace/ci-green-workflow.test.ts +84 -1
  286. package/src/workspace/ci-green-workflow.ts +139 -2
  287. package/src/workspace/ci-green.test.ts +5 -2
  288. package/src/workspace/ci-green.ts +359 -27
  289. package/src/workspace/ci-init.test.ts +159 -0
  290. package/src/workspace/ci-init.ts +445 -0
  291. package/src/workspace/ci-last-green.schema.json +49 -0
  292. package/src/workspace/ci-list.schema.json +35 -1
  293. package/src/workspace/ci-report-url.test.ts +6 -1
  294. package/src/workspace/ci-run-index.schema.json +1 -1
  295. package/src/workspace/ci-run-index.ts +1 -1
  296. package/src/workspace/ci-run.test.ts +24 -7
  297. package/src/workspace/ci-run.ts +78 -16
  298. package/src/workspace/ci-runner-start.test.ts +151 -0
  299. package/src/workspace/ci-runner-start.ts +193 -0
  300. package/src/workspace/ci-serve-builds.test.ts +86 -0
  301. package/src/workspace/ci-serve.test.ts +59 -0
  302. package/src/workspace/ci-serve.ts +177 -22
  303. package/src/workspace/ci-workflow.ts +71 -0
  304. package/src/workspace/declaration.schema.json +51 -0
  305. package/src/workspace/declaration.test.ts +17 -0
  306. package/src/workspace/declaration.ts +53 -3
  307. package/src/workspace/init.ts +2 -2
  308. package/src/workspace/ls.schema.json +2 -1
  309. package/src/workspace/ls.ts +3 -2
  310. package/src/workspace/member-pipeline.ts +2 -1
  311. package/src/workspace/merge-record.schema.json +49 -0
  312. package/src/workspace/pr-report.schema.json +3 -3
  313. package/src/workspace/reason-codes.test.ts +4 -0
  314. package/src/workspace/reason-codes.ts +28 -0
  315. package/src/workspace/release-candidates.schema.json +353 -0
  316. package/src/workspace/release-candidates.test.ts +106 -0
  317. package/src/workspace/release-candidates.ts +315 -0
  318. package/src/workspace/release-workflow.ts +167 -0
  319. package/src/workspace/security.test.ts +70 -0
  320. package/src/workspace/security.ts +118 -0
@@ -37,6 +37,8 @@
37
37
  * | WSP126, WSP127 | box intent: the decision record a box names exists, and constrains a member or path of this workspace (#2850, #2857) |
38
38
  * | WSP141, WSP142 | member links resolved against a live read, with `--live` (#2549) |
39
39
  * | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
40
+ * | WSP151 | what a member under `ci.workflow` still lacks (#4168 step 6) |
41
+ * | WSP161 to WSP165 | the security groups the declaration opts into: what each still lacks (#4168 step 7) |
40
42
  */
41
43
  import type { LintDiagnostic, LintRule, Severity } from "../lint/rule.js";
42
44
  import type { PostSynthDiagnostic } from "../lint/post-synth.js";
@@ -1 +1 @@
1
- {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/workspace/checks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/workspace/checks.ts"],"names":[],"mappings":"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"}
@@ -37,6 +37,8 @@
37
37
  * | WSP126, WSP127 | box intent: the decision record a box names exists, and constrains a member or path of this workspace (#2850, #2857) |
38
38
  * | WSP141, WSP142 | member links resolved against a live read, with `--live` (#2549) |
39
39
  * | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
40
+ * | WSP151 | what a member under `ci.workflow` still lacks (#4168 step 6) |
41
+ * | WSP161 to WSP165 | the security groups the declaration opts into: what each still lacks (#4168 step 7) |
40
42
  */
41
43
  import { realpathSync } from "node:fs";
42
44
  import { relative, sep } from "node:path";
@@ -53,6 +55,8 @@ import { gatherLiveLinkFacts, LIVE_CHECKS } from "./checks/live.js";
53
55
  import { RECORD_CHECKS } from "./checks/records.js";
54
56
  import { BOX_CHECKS } from "./checks/boxes.js";
55
57
  import { DIAGRAM_CHECKS } from "./checks/diagrams.js";
58
+ import { SECURITY_CHECKS } from "./checks/security.js";
59
+ import { CI_WORKFLOW_CHECKS } from "./checks/ci-workflow.js";
56
60
  import { loadDeclaredKinds } from "./declared-kinds.js";
57
61
  import { resolveBoxIntents } from "./box-intent.js";
58
62
  import { gatherLexiconOutputs } from "./lexicon-outputs.js";
@@ -270,6 +274,10 @@ export const WORKSPACE_CHECKS = [
270
274
  ...DIAGRAM_CHECKS,
271
275
  // Links resolved against a live read, with --live (#2549), WSP141 and WSP142.
272
276
  ...LIVE_CHECKS,
277
+ // What a member under ci.workflow still lacks (#4168 step 6), WSP151.
278
+ ...CI_WORKFLOW_CHECKS,
279
+ // The security groups the declaration opts into (#4168 step 7), WSP161 to WSP165.
280
+ ...SECURITY_CHECKS,
273
281
  ];
274
282
  const BY_ID = new Map(WORKSPACE_CHECKS.map((c) => [c.id, c]));
275
283
  function settingProblem(id) {
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Which commits get built, and what each one ends with (#4169).
3
+ *
4
+ * A `ci.green` phase declares `build: "every"` (the default) or
5
+ * `build: "newest"`. Under `newest` the phase's workflow keeps one run
6
+ * waiting and drops the rest, so only the newest commit is built when a
7
+ * runner frees. This module does the tick's part of that, given the
8
+ * verdicts {@link planTick} (./ci-green.ts) judged:
9
+ *
10
+ * - Superseded. A commit with no run of a `newest` phase, below a newer
11
+ * commit that has one, is `unbuilt` for that phase, covered by the newer
12
+ * commit. A cancelled run makes any phase `unbuilt` (`phaseVerdictOf`).
13
+ * - Retry. A failed run with one attempt is run again once before the
14
+ * commit counts red: through the forge's re-run (`CiForge.rerun`) for
15
+ * check runs, or a build request for chant runs. A retry that has not
16
+ * started an hour on counts as the failure it was.
17
+ * - Bisect. When the newest built commit of a `newest` phase is red, the
18
+ * unbuilt commits back to the last one the phase passed on are built, one
19
+ * request per tick: the commit halfway when more than four are left, else
20
+ * the one just below the red. The first red commit found is the culprit,
21
+ * and it and every commit after it name it.
22
+ * - Requests. A build is asked for with an annotated tag,
23
+ * `ci/build/<sha>`, whose record names the phases and why. The tick
24
+ * starts a workflow at that tag on GitHub (`CiForge.dispatch`), and `ci
25
+ * serve --build-command` serves it for chant runs. A phase a request
26
+ * waits on is `pending`. A request no runner served in six hours leaves
27
+ * the phase `unbuilt`, and once its phases are decided the tick deletes
28
+ * the tag.
29
+ * - Records. Every first-parent commit the tick judged gets
30
+ * `_ci/<sha>.json` on `chant/lifecycle`, rewritten only when what it says
31
+ * changes: `green`, `red`, `unbuilt` or `pending`, with each required
32
+ * phase's verdict, runs, reason, the commit that covers it and the culprit.
33
+ * A commit that leaves the window still `pending` is rewritten `unbuilt`,
34
+ * for the reason `aged-out`. The `ci/green` and `ci/revoked` tags stay
35
+ * what decides a release; the records say what happened to the rest.
36
+ */
37
+ import type { CiBuild, CiGreen } from "./declaration.js";
38
+ import type { CheckRun } from "./ci-green-forge.js";
39
+ import { type PhaseReason, type TickCommit, type Verdict } from "./ci-green.js";
40
+ export declare const CI_BUILD_TAG_PREFIX = "ci/build/";
41
+ /** The directory on `chant/lifecycle` holding one record per commit. */
42
+ export declare const CI_RECORDS_DIR = "_ci";
43
+ /** How long a build request may wait for a runner before its phases count as unbuilt. */
44
+ export declare const BUILD_REQUEST_TTL_MS: number;
45
+ /** How long a retry may take to start before the failure it was counts. */
46
+ export declare const RETRY_TTL_MS = 3600000;
47
+ /** Above this many unbuilt commits a bisect halves the range; at or below it, it steps back one commit at a time. */
48
+ export declare const BISECT_HALVE_ABOVE = 4;
49
+ export type BuildRequestReason = "bisect" | "retry" | "requested";
50
+ /** A `ci/build/<sha>` tag's record: the phases to build at the commit, and why. */
51
+ export interface BuildRequest {
52
+ schema: 1;
53
+ tag: string;
54
+ commit: string;
55
+ branch: string;
56
+ phases: string[];
57
+ reason: BuildRequestReason;
58
+ /** When it was asked for, ISO 8601. */
59
+ at: string;
60
+ }
61
+ /** One record, `_ci/<sha>.json` on `chant/lifecycle`. */
62
+ export interface CiRecord {
63
+ schema: 1;
64
+ commit: string;
65
+ branch: string;
66
+ /** `green` and `red` follow the tags: a green tag, or a failed phase or a revoked tag. */
67
+ state: "green" | "red" | "unbuilt" | "pending";
68
+ /** When this content was written, ISO 8601. Not compared when deciding whether to write again. */
69
+ at: string;
70
+ /** Set when the commit has a revoked tag. */
71
+ revoked?: true;
72
+ phases: Record<string, CiRecordPhase>;
73
+ /** The first required phase that failed, and its run, when the state is red. */
74
+ failure?: {
75
+ phase: string;
76
+ run: CiRecordRun;
77
+ };
78
+ /** The retries asked for, by run name: the run that failed and when. */
79
+ retried?: Record<string, {
80
+ run: number;
81
+ at: string;
82
+ }>;
83
+ /** The build request waiting on this commit, when there is one. */
84
+ request?: {
85
+ tag: string;
86
+ reason: BuildRequestReason;
87
+ phases: string[];
88
+ at: string;
89
+ dispatched?: true;
90
+ };
91
+ }
92
+ export interface CiRecordRun {
93
+ name: string;
94
+ id: number | null;
95
+ conclusion: string | null;
96
+ url: string | null;
97
+ }
98
+ export interface CiRecordPhase {
99
+ verdict: Verdict;
100
+ build: CiBuild;
101
+ runs: CiRecordRun[];
102
+ reason?: PhaseReason | "aged-out";
103
+ coveredBy?: string;
104
+ culprit?: string;
105
+ }
106
+ /** How a tick can run a failed job again: the forge's re-run, a build request, or not at all. */
107
+ export type RetryVia = "forge" | "request" | null;
108
+ export interface BuildsInput {
109
+ green: CiGreen;
110
+ /** Newest first, as {@link planTick} returns them. */
111
+ commits: TickCommit[];
112
+ /** Every attempt of every run, by commit. */
113
+ runs: ReadonlyMap<string, readonly CheckRun[]>;
114
+ requests: ReadonlyMap<string, BuildRequest>;
115
+ records: ReadonlyMap<string, CiRecord>;
116
+ now: Date;
117
+ retryVia: RetryVia;
118
+ }
119
+ export interface BuildsPlan {
120
+ /** Requests to make: bisect steps, and retries of chant runs. */
121
+ requests: BuildRequest[];
122
+ /** Failed check runs to run again through the forge, by commit. */
123
+ retries: {
124
+ sha: string;
125
+ run: CheckRun;
126
+ }[];
127
+ /** Request tags whose phases are decided, or that waited too long, or whose commit left the window. */
128
+ resolved: string[];
129
+ }
130
+ /**
131
+ * Apply supersession, retries, requests and bisection to the judged commits,
132
+ * in place, and say what the tick has to ask for. Pure: it reads and writes
133
+ * nothing but its input.
134
+ */
135
+ export declare function planBuilds(input: BuildsInput): BuildsPlan;
136
+ /** The record a judged commit gets, carrying the retries asked for before and now. */
137
+ export declare function recordOf(input: {
138
+ green: CiGreen;
139
+ commit: TickCommit;
140
+ previous?: CiRecord;
141
+ retries: readonly {
142
+ sha: string;
143
+ run: CheckRun;
144
+ }[];
145
+ requests: ReadonlyMap<string, BuildRequest>;
146
+ now: Date;
147
+ }): CiRecord;
148
+ /** The record a commit that left the window still pending gets: each pending phase `unbuilt`, `aged-out`. */
149
+ export declare function agedOut(record: CiRecord, now: Date): CiRecord;
150
+ /** Whether two records say the same thing, the time each was written aside. */
151
+ export declare function sameRecord(a: CiRecord | undefined, b: CiRecord): boolean;
152
+ export declare const recordPath: (sha: string) => string;
153
+ /** One commit's record, from the local `chant/lifecycle`. */
154
+ export declare function readCiRecord(top: string, sha: string): CiRecord | undefined;
155
+ /** Every record on the local `chant/lifecycle`, by commit. Empty when the branch or the directory is missing. */
156
+ export declare function readCiRecords(top: string): Map<string, CiRecord>;
157
+ /** Every `ci/build/<sha>` tag with a record this reads, from local refs, by commit. */
158
+ export declare function readBuildRequests(top: string): Map<string, BuildRequest>;
159
+ /** The annotation of a request tag: a subject line, then the record. */
160
+ export declare function buildRequestAnnotation(r: BuildRequest): string;
161
+ /** A request for `ci run --at <sha>`: every required phase, asked for by a person. */
162
+ export declare function requestedBuild(green: CiGreen, commit: string, phases: readonly string[], now: Date): BuildRequest;
163
+ //# sourceMappingURL=ci-builds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-builds.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-builds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Which commits get built, and what each one ends with (#4169).
3
+ *
4
+ * A `ci.green` phase declares `build: "every"` (the default) or
5
+ * `build: "newest"`. Under `newest` the phase's workflow keeps one run
6
+ * waiting and drops the rest, so only the newest commit is built when a
7
+ * runner frees. This module does the tick's part of that, given the
8
+ * verdicts {@link planTick} (./ci-green.ts) judged:
9
+ *
10
+ * - Superseded. A commit with no run of a `newest` phase, below a newer
11
+ * commit that has one, is `unbuilt` for that phase, covered by the newer
12
+ * commit. A cancelled run makes any phase `unbuilt` (`phaseVerdictOf`).
13
+ * - Retry. A failed run with one attempt is run again once before the
14
+ * commit counts red: through the forge's re-run (`CiForge.rerun`) for
15
+ * check runs, or a build request for chant runs. A retry that has not
16
+ * started an hour on counts as the failure it was.
17
+ * - Bisect. When the newest built commit of a `newest` phase is red, the
18
+ * unbuilt commits back to the last one the phase passed on are built, one
19
+ * request per tick: the commit halfway when more than four are left, else
20
+ * the one just below the red. The first red commit found is the culprit,
21
+ * and it and every commit after it name it.
22
+ * - Requests. A build is asked for with an annotated tag,
23
+ * `ci/build/<sha>`, whose record names the phases and why. The tick
24
+ * starts a workflow at that tag on GitHub (`CiForge.dispatch`), and `ci
25
+ * serve --build-command` serves it for chant runs. A phase a request
26
+ * waits on is `pending`. A request no runner served in six hours leaves
27
+ * the phase `unbuilt`, and once its phases are decided the tick deletes
28
+ * the tag.
29
+ * - Records. Every first-parent commit the tick judged gets
30
+ * `_ci/<sha>.json` on `chant/lifecycle`, rewritten only when what it says
31
+ * changes: `green`, `red`, `unbuilt` or `pending`, with each required
32
+ * phase's verdict, runs, reason, the commit that covers it and the culprit.
33
+ * A commit that leaves the window still `pending` is rewritten `unbuilt`,
34
+ * for the reason `aged-out`. The `ci/green` and `ci/revoked` tags stay
35
+ * what decides a release; the records say what happened to the rest.
36
+ */
37
+ import { execFileSync } from "node:child_process";
38
+ import { commitVerdictOf, phaseVerdictOf } from "./ci-green.js";
39
+ export const CI_BUILD_TAG_PREFIX = "ci/build/";
40
+ /** The directory on `chant/lifecycle` holding one record per commit. */
41
+ export const CI_RECORDS_DIR = "_ci";
42
+ const LEDGER_REF = "refs/heads/chant/lifecycle";
43
+ /** How long a build request may wait for a runner before its phases count as unbuilt. */
44
+ export const BUILD_REQUEST_TTL_MS = 6 * 3600000;
45
+ /** How long a retry may take to start before the failure it was counts. */
46
+ export const RETRY_TTL_MS = 3600000;
47
+ /** Above this many unbuilt commits a bisect halves the range; at or below it, it steps back one commit at a time. */
48
+ export const BISECT_HALVE_ABOVE = 4;
49
+ const iso = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
50
+ const phaseOf = (green, name) => green.phases.find((p) => p.name === name);
51
+ const buildOf = (green, name) => phaseOf(green, name).build ?? "every";
52
+ const decided = (v) => v === "pass" || v === "fail";
53
+ const age = (at, now) => now.getTime() - Date.parse(at);
54
+ function setPhase(c, phase, fn) {
55
+ const v = c.verdict;
56
+ if (!v)
57
+ return;
58
+ c.verdict = commitVerdictOf(v.phases.map((p) => (p.phase === phase ? fn(p) : p)));
59
+ }
60
+ const phaseIn = (c, phase) => c.verdict?.phases.find((p) => p.phase === phase);
61
+ /**
62
+ * Apply supersession, retries, requests and bisection to the judged commits,
63
+ * in place, and say what the tick has to ask for. Pure: it reads and writes
64
+ * nothing but its input.
65
+ */
66
+ export function planBuilds(input) {
67
+ const { green, commits, now } = input;
68
+ const plan = { requests: [], retries: [], resolved: [] };
69
+ const inWindow = new Set(commits.map((c) => c.sha));
70
+ const live = (sha, phase) => {
71
+ const r = input.requests.get(sha);
72
+ return r && r.phases.includes(phase) && age(r.at, now) < BUILD_REQUEST_TTL_MS ? r : undefined;
73
+ };
74
+ // A request waits on a phase with no run yet; one no runner served in time leaves it unbuilt.
75
+ for (const c of commits) {
76
+ const r = input.requests.get(c.sha);
77
+ if (!r || !c.verdict)
78
+ continue;
79
+ for (const name of r.phases) {
80
+ const p = phaseIn(c, name);
81
+ if (!p || p.runs.length > 0)
82
+ continue;
83
+ setPhase(c, name, (q) => (live(c.sha, name) ? { ...q, verdict: "pending", reason: "requested" } : { ...q, verdict: "unbuilt", reason: "request-unserved" }));
84
+ }
85
+ }
86
+ // Superseded: newest first, a newest phase with no run below a commit with one.
87
+ for (const name of green.require) {
88
+ if (buildOf(green, name) !== "newest")
89
+ continue;
90
+ let cover;
91
+ for (const c of commits) {
92
+ const p = phaseIn(c, name);
93
+ if (!p)
94
+ continue;
95
+ if (p.runs.length > 0) {
96
+ cover = c.sha;
97
+ continue;
98
+ }
99
+ if (cover && p.reason !== "requested" && p.reason !== "request-unserved")
100
+ setPhase(c, name, (q) => ({ ...q, verdict: "unbuilt", reason: "superseded", coveredBy: cover }));
101
+ }
102
+ }
103
+ // Retry once: a commit with no tag whose failed run has one attempt.
104
+ if (input.retryVia) {
105
+ for (const c of commits) {
106
+ if (c.before !== "none" || c.verdict?.verdict !== "fail")
107
+ continue;
108
+ const all = input.runs.get(c.sha) ?? [];
109
+ const retried = input.records.get(c.sha)?.retried ?? {};
110
+ for (const p of c.verdict.phases) {
111
+ for (const run of p.runs.filter((r) => r.verdict === "fail")) {
112
+ if (all.filter((r) => r.name === run.name).length > 1)
113
+ continue;
114
+ const asked = retried[run.name];
115
+ if (asked && asked.run === run.id && age(asked.at, now) >= RETRY_TTL_MS)
116
+ continue;
117
+ if (!asked || asked.run !== run.id) {
118
+ const checkRun = all.find((r) => r.name === run.name && r.id === run.id);
119
+ if (input.retryVia === "forge")
120
+ plan.retries.push({ sha: c.sha, run: checkRun });
121
+ else if (!input.requests.has(c.sha) && !plan.requests.some((q) => q.commit === c.sha))
122
+ plan.requests.push(request(green, c.sha, [p.phase], "retry", now));
123
+ }
124
+ setPhase(c, p.phase, (q) => {
125
+ const runs = q.runs.map((r) => (r.name === run.name && r.id === run.id ? { ...r, verdict: "pending" } : r));
126
+ return { ...q, runs, ...phaseVerdictOf(runs, q.unmatched), reason: "retrying" };
127
+ });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ // Culprits for every phase, and a bisect request for a newest one.
133
+ for (const name of green.require) {
134
+ const verdicts = commits.map((c) => phaseIn(c, name));
135
+ const top = verdicts.findIndex((p) => decided(p?.verdict));
136
+ if (top < 0 || verdicts[top].verdict !== "fail")
137
+ continue;
138
+ let oldestRed = top;
139
+ let between = [];
140
+ let passed = -1;
141
+ for (let i = top + 1; i < commits.length; i++) {
142
+ const v = verdicts[i]?.verdict;
143
+ if (v === "fail") {
144
+ oldestRed = i;
145
+ between = [];
146
+ }
147
+ else if (v === "pass") {
148
+ passed = i;
149
+ break;
150
+ }
151
+ else if (v !== undefined)
152
+ between.push(i);
153
+ }
154
+ if (between.length === 0) {
155
+ if (passed < 0)
156
+ continue;
157
+ const culprit = commits[oldestRed].sha;
158
+ for (let i = oldestRed; i >= 0; i--) {
159
+ if (verdicts[i]?.verdict === "pass")
160
+ break;
161
+ if (verdicts[i])
162
+ setPhase(commits[i], name, (q) => ({ ...q, culprit }));
163
+ }
164
+ continue;
165
+ }
166
+ if (buildOf(green, name) !== "newest")
167
+ continue;
168
+ if (between.some((i) => live(commits[i].sha, name)))
169
+ continue;
170
+ const pick = between.length > BISECT_HALVE_ABOVE ? between[Math.floor(between.length / 2)] : between[0];
171
+ const sha = commits[pick].sha;
172
+ if (input.requests.has(sha) || plan.requests.some((q) => q.commit === sha))
173
+ continue;
174
+ plan.requests.push(request(green, sha, [name], "bisect", now));
175
+ setPhase(commits[pick], name, (q) => ({ ...q, verdict: "pending", reason: "requested", coveredBy: undefined }));
176
+ }
177
+ // A request is done once its phases are decided, it has waited too long, or its commit left the window.
178
+ for (const [sha, r] of input.requests) {
179
+ const c = commits.find((x) => x.sha === sha);
180
+ const done = !inWindow.has(sha) || age(r.at, now) >= BUILD_REQUEST_TTL_MS || (c !== undefined && (c.before === "revoked" || r.phases.every((name) => decided(phaseIn(c, name)?.verdict))));
181
+ if (done)
182
+ plan.resolved.push(r.tag);
183
+ }
184
+ return plan;
185
+ }
186
+ function request(green, commit, phases, reason, now) {
187
+ return { schema: 1, tag: `${CI_BUILD_TAG_PREFIX}${commit}`, commit, branch: green.branch, phases, reason, at: iso(now) };
188
+ }
189
+ // ── Records ──────────────────────────────────────────────────────────────────
190
+ const recordRun = (r) => ({ name: r.name, id: r.opRun ? null : r.id, conclusion: r.conclusion, url: r.url });
191
+ /** The record a judged commit gets, carrying the retries asked for before and now. */
192
+ export function recordOf(input) {
193
+ const { green, commit: c, previous, now } = input;
194
+ const retried = { ...(previous?.retried ?? {}) };
195
+ for (const r of input.retries)
196
+ if (r.sha === c.sha)
197
+ retried[r.run.name] = { run: r.run.id, at: iso(now) };
198
+ const req = input.requests.get(c.sha);
199
+ const extras = {
200
+ ...(Object.keys(retried).length > 0 ? { retried } : {}),
201
+ ...(req ? { request: { tag: req.tag, reason: req.reason, phases: req.phases, at: req.at } } : {}),
202
+ };
203
+ if (c.before === "revoked" || !c.verdict) {
204
+ return { schema: 1, commit: c.sha, branch: green.branch, state: "red", at: iso(now), revoked: true, phases: previous?.phases ?? {}, ...(previous?.failure ? { failure: previous.failure } : {}), ...extras };
205
+ }
206
+ const v = c.verdict;
207
+ // A pass whose tag an ignored tag blocks (#4031) is not green until a person clears the name.
208
+ const state = c.before === "green" ? (v.verdict === "fail" ? "red" : "green") : v.verdict === "pass" ? (c.made ? "green" : "pending") : v.verdict === "fail" ? "red" : v.verdict;
209
+ const phases = {};
210
+ for (const p of v.phases) {
211
+ phases[p.phase] = {
212
+ verdict: p.verdict,
213
+ build: buildOf(green, p.phase),
214
+ runs: p.runs.map(recordRun),
215
+ ...(p.reason ? { reason: p.reason } : {}),
216
+ ...(p.coveredBy ? { coveredBy: p.coveredBy } : {}),
217
+ ...(p.culprit ? { culprit: p.culprit } : {}),
218
+ };
219
+ }
220
+ return {
221
+ schema: 1,
222
+ commit: c.sha,
223
+ branch: green.branch,
224
+ state,
225
+ at: iso(now),
226
+ phases,
227
+ ...(v.failure ? { failure: { phase: v.failure.phase, run: recordRun(v.failure.run) } } : {}),
228
+ ...extras,
229
+ };
230
+ }
231
+ /** The record a commit that left the window still pending gets: each pending phase `unbuilt`, `aged-out`. */
232
+ export function agedOut(record, now) {
233
+ const phases = Object.fromEntries(Object.entries(record.phases).map(([k, p]) => [k, p.verdict === "pending" ? { ...p, verdict: "unbuilt", reason: "aged-out" } : p]));
234
+ const { request: _request, ...rest } = record;
235
+ return { ...rest, state: "unbuilt", at: iso(now), phases };
236
+ }
237
+ /** Whether two records say the same thing, the time each was written aside. */
238
+ export function sameRecord(a, b) {
239
+ if (!a)
240
+ return false;
241
+ const { at: _a, ...x } = a;
242
+ const { at: _b, ...y } = b;
243
+ return JSON.stringify(x) === JSON.stringify(y);
244
+ }
245
+ export const recordPath = (sha) => `${CI_RECORDS_DIR}/${sha}.json`;
246
+ function tryGit(top, args) {
247
+ try {
248
+ return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 }).trimEnd();
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ function parseRecord(text) {
255
+ if (!text)
256
+ return undefined;
257
+ try {
258
+ const r = JSON.parse(text);
259
+ return r && r.schema === 1 && typeof r.commit === "string" && typeof r.state === "string" ? r : undefined;
260
+ }
261
+ catch {
262
+ return undefined;
263
+ }
264
+ }
265
+ /** One commit's record, from the local `chant/lifecycle`. */
266
+ export function readCiRecord(top, sha) {
267
+ return parseRecord(tryGit(top, ["show", `${LEDGER_REF}:${recordPath(sha)}`]));
268
+ }
269
+ /** Every record on the local `chant/lifecycle`, by commit. Empty when the branch or the directory is missing. */
270
+ export function readCiRecords(top) {
271
+ const out = new Map();
272
+ const names = tryGit(top, ["ls-tree", "--name-only", `${LEDGER_REF}:${CI_RECORDS_DIR}`]);
273
+ if (!names)
274
+ return out;
275
+ for (const name of names.split("\n")) {
276
+ const m = /^([0-9a-f]{40,64})\.json$/.exec(name);
277
+ if (!m)
278
+ continue;
279
+ const r = readCiRecord(top, m[1]);
280
+ if (r)
281
+ out.set(m[1], r);
282
+ }
283
+ return out;
284
+ }
285
+ // ── Requests ─────────────────────────────────────────────────────────────────
286
+ /** Every `ci/build/<sha>` tag with a record this reads, from local refs, by commit. */
287
+ export function readBuildRequests(top) {
288
+ const out = new Map();
289
+ const raw = tryGit(top, ["for-each-ref", "--format=%(refname:lstrip=2)%00%(objecttype)%00%(contents)%01", `refs/tags/${CI_BUILD_TAG_PREFIX}`]);
290
+ if (!raw)
291
+ return out;
292
+ for (const entry of raw.split("\u0001")) {
293
+ const [tag, type, contents] = entry.replace(/^\n/, "").split("\u0000");
294
+ if (!tag || type !== "tag" || !contents?.includes("{"))
295
+ continue;
296
+ try {
297
+ const r = JSON.parse(contents.slice(contents.indexOf("{")));
298
+ const sha = tag.slice(CI_BUILD_TAG_PREFIX.length);
299
+ if (r.schema === 1 && r.commit === sha && Array.isArray(r.phases) && typeof r.at === "string")
300
+ out.set(sha, { ...r, tag });
301
+ }
302
+ catch {
303
+ // A tag whose record does not parse asks for nothing.
304
+ }
305
+ }
306
+ return out;
307
+ }
308
+ /** The annotation of a request tag: a subject line, then the record. */
309
+ export function buildRequestAnnotation(r) {
310
+ return `ci: build ${r.phases.join(", ")} at this commit (${r.reason})\n\n${JSON.stringify(r, null, 2)}\n`;
311
+ }
312
+ /** A request for `ci run --at <sha>`: every required phase, asked for by a person. */
313
+ export function requestedBuild(green, commit, phases, now) {
314
+ return request(green, commit, [...phases], "requested", now);
315
+ }
@@ -8,7 +8,8 @@
8
8
  * tag the commits that turned green, revoke the ones that turned red
9
9
  * ci workflow [--workflow <name>]... [--output <file>] [--chant <command>] [--install <command>]
10
10
  * [--token-secret <NAME> | --app-id-var <VAR> --app-key-secret <NAME>] [--runner-key-secret <NAME>]
11
- * write the root CI file that runs the tick
11
+ * write the root CI file that runs the tick, and the triggers and
12
+ * concurrency of the workflows holding a phase that sets build (#4169)
12
13
  *
13
14
  * The tick signs its tags with a runner key (#4031) from `--key <file>` or
14
15
  * the PEM in `CHANT_RUNNER_KEY`, which the generated workflow sets from the
@@ -1 +1 @@
1
- {"version":3,"file":"ci-green-cli.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-green-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAmE,cAAc,EAAE,MAAM,YAAY,CAAC;AAW7G,OAAO,EAAE,cAAc,EAAE,CAAC;AAe1B,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBzE;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBpE;AAID,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAapE;AAeD,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDxE"}
1
+ {"version":3,"file":"ci-green-cli.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-green-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAmE,cAAc,EAAE,MAAM,YAAY,CAAC;AAW7G,OAAO,EAAE,cAAc,EAAE,CAAC;AAe1B,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBzE;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBpE;AAID,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBpE;AAeD,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DxE"}
@@ -8,7 +8,8 @@
8
8
  * tag the commits that turned green, revoke the ones that turned red
9
9
  * ci workflow [--workflow <name>]... [--output <file>] [--chant <command>] [--install <command>]
10
10
  * [--token-secret <NAME> | --app-id-var <VAR> --app-key-secret <NAME>] [--runner-key-secret <NAME>]
11
- * write the root CI file that runs the tick
11
+ * write the root CI file that runs the tick, and the triggers and
12
+ * concurrency of the workflows holding a phase that sets build (#4169)
12
13
  *
13
14
  * The tick signs its tags with a runner key (#4031) from `--key <file>` or
14
15
  * the PEM in `CHANT_RUNNER_KEY`, which the generated workflow sets from the
@@ -28,7 +29,7 @@ import { formatError } from "../cli/format.js";
28
29
  import { findWorkspaceRoot } from "../project-root.js";
29
30
  import { ciList, ciTick, CiGreenError, formatList, formatTick, lastGreen, RUNNER_KEY_ENV } from "./ci-green.js";
30
31
  import { ciForgeFromEnv } from "./ci-green-forge.js";
31
- import { ACTIONS_NAME, CI_GREEN_WORKFLOW_FILE, ciGreenHeader, readWorkflowSources, renderCiGreenWorkflow, workflowsHolding, writeWorkflowFile } from "./ci-green-workflow.js";
32
+ import { ACTIONS_NAME, buildTriggerEdits, CI_GREEN_WORKFLOW_FILE, ciGreenHeader, readWorkflowSources, renderCiGreenWorkflow, workflowsHolding, writeWorkflowFile } from "./ci-green-workflow.js";
32
33
  import { readDeclaration, readerVersion, WorkspaceReadError } from "./declaration.js";
33
34
  import { recordGeneratedFiles, resolveMemberContext, shellArg } from "./member-pipeline.js";
34
35
  import { gitTop, workingTree } from "./tree.js";
@@ -99,6 +100,13 @@ export async function runCiTick(ctx) {
99
100
  // The forge is made only when the source is check runs, so a tick from chant runs needs no forge token.
100
101
  const forge = () => ciForgeFromEnv(ctx.args.forge ?? "github");
101
102
  const runnerKey = ctx.args.key !== undefined ? readFileSync(resolve(ctx.args.key), "utf-8") : process.env[RUNNER_KEY_ENV] || undefined;
103
+ // #4168: under who-ran-it every tag the tick makes is signed, so a tick without a key makes none.
104
+ if (runnerKey === undefined && ctx.args.dryRun !== true) {
105
+ const { securityGroupsAt } = await import("./security.js");
106
+ if (securityGroupsAt(process.cwd()).has("who-ran-it")) {
107
+ throw new Error(`the workspace opts into who-ran-it, so the tick signs every tag it makes and needs a runner key; pass --key <runner key.pem> or set ${RUNNER_KEY_ENV}`);
108
+ }
109
+ }
102
110
  const result = await ciTick({ cwd: process.cwd(), forge, dryRun: ctx.args.dryRun === true, ...(source ? { source } : {}), ...(runnerKey !== undefined ? { runnerKey } : {}) });
103
111
  console.log(formatTick(result, ctx.args.dryRun === true));
104
112
  return 0;
@@ -142,7 +150,10 @@ export async function runCiWorkflow(ctx) {
142
150
  const member = resolveMemberContext(cwd, found);
143
151
  const target = ctx.args.output ? resolve(realpathSync(cwd), ctx.args.output) : join(repoRoot, CI_GREEN_WORKFLOW_FILE);
144
152
  const fromRepo = relative(repoRoot, target).split(sep).join("/");
145
- const scan = workflowsHolding(green, readWorkflowSources(repoRoot, fromRepo));
153
+ const sources = readWorkflowSources(repoRoot, fromRepo);
154
+ const scan = workflowsHolding(green, sources);
155
+ // #4169: the triggers and concurrency of the workflows holding a phase that sets build. Refused before anything is written.
156
+ const edits = buildTriggerEdits(green, sources);
146
157
  const extra = ctx.args.ciWorkflows ?? [];
147
158
  const workflows = [...new Set([...scan.workflows, ...extra])].sort();
148
159
  for (const f of scan.unreadable)
@@ -167,8 +178,9 @@ export async function runCiWorkflow(ctx) {
167
178
  const runnerKeySecret = ctx.args.ciRunnerKeySecret;
168
179
  if (runnerKeySecret)
169
180
  parts.push("--runner-key-secret", runnerKeySecret);
170
- if (green.signedSince && !runnerKeySecret) {
171
- console.error(`warning: ci.green.signedSince is set, so the tick needs a runner key; pass --runner-key-secret <NAME> naming the repository secret that holds it, or every tick will fail`);
181
+ const { securityGroupsAt } = await import("./security.js");
182
+ if ((green.signedSince || securityGroupsAt(root).has("who-ran-it")) && !runnerKeySecret) {
183
+ console.error(`warning: ci.green.signedSince is set or the workspace opts into who-ran-it, so the tick needs a runner key; pass --runner-key-secret <NAME> naming the repository secret that holds it, or every tick will fail`);
172
184
  }
173
185
  const command = parts.map(shellArg).join(" ");
174
186
  const workspaceRoot = relative(repoRoot, root).split(sep).join("/") || ".";
@@ -179,6 +191,14 @@ export async function runCiWorkflow(ctx) {
179
191
  if (member)
180
192
  recordGeneratedFiles(member.memberRoot, [{ path: fromRepo, command }]);
181
193
  console.log(`Wrote ${written}: ticks when ${workflows.join(", ")} complete${workflows.length === 1 ? "s" : ""} on ${green.branch}, and every 15 minutes.`);
194
+ for (const e of edits) {
195
+ if (!e.changed)
196
+ continue;
197
+ const path = writeWorkflowFile(join(repoRoot, e.path), e.text);
198
+ console.log(e.build === "newest"
199
+ ? `Wrote ${path}'s triggers and concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} the newest commit on ${green.branch} when a run ends, and a ci/build/<sha> tag by hand.`
200
+ : `Wrote ${path}'s concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} every commit on ${green.branch}, none cancelled.`);
201
+ }
182
202
  return 0;
183
203
  }
184
204
  catch (err) {
@@ -51,6 +51,18 @@ export interface CiForge {
51
51
  kind: ForgeKind;
52
52
  /** Every check run on `sha`, every attempt. */
53
53
  checkRuns(sha: string): Promise<CheckRun[]>;
54
+ /**
55
+ * Run a failed check run's job again (#4169), so a flake costs one more
56
+ * run before the commit counts red. Absent on a forge chant can't ask.
57
+ */
58
+ rerun?(run: CheckRun): Promise<void>;
59
+ /**
60
+ * Start a workflow at a ref (#4169), for a build the tick asks for: a
61
+ * bisect step, or `chant ci run --at`. `workflow` is the file's name in
62
+ * `.github/workflows/`, and `ref` a branch or tag. Absent on a forge chant
63
+ * can't ask.
64
+ */
65
+ dispatch?(workflow: string, ref: string): Promise<void>;
54
66
  }
55
67
  export interface GithubCiForgeOptions {
56
68
  /** `$GITHUB_API_URL`: `https://api.github.com` or `<host>/api/v3`. */