@intentius/chant 0.124.32 → 0.124.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) 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 +16 -1
  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 +81 -21
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +23 -1
  24. package/dist/cli/registry.d.ts +18 -4
  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/release-compile.d.ts +37 -2
  40. package/dist/components/release-compile.d.ts.map +1 -1
  41. package/dist/components/release-compile.js +64 -8
  42. package/dist/components/release-promote.d.ts +17 -3
  43. package/dist/components/release-promote.d.ts.map +1 -1
  44. package/dist/components/release-promote.js +79 -18
  45. package/dist/components/verbs/bom-writer.d.ts +1 -1
  46. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  47. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  48. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  49. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  50. package/dist/components/wave-plan.d.ts +10 -0
  51. package/dist/components/wave-plan.d.ts.map +1 -1
  52. package/dist/components/wave-plan.js +32 -4
  53. package/dist/graph-detail.d.ts.map +1 -1
  54. package/dist/graph-detail.js +32 -7
  55. package/dist/health.d.ts +110 -0
  56. package/dist/health.d.ts.map +1 -0
  57. package/dist/health.js +105 -0
  58. package/dist/health.schema.json +191 -0
  59. package/dist/lexicon.d.ts +27 -0
  60. package/dist/lexicon.d.ts.map +1 -1
  61. package/dist/lifecycle/merge-record.d.ts +104 -0
  62. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  63. package/dist/lifecycle/merge-record.js +102 -0
  64. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  65. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  66. package/dist/lifecycle/publish-attachments.js +27 -13
  67. package/dist/lifecycle/publish-npm.d.ts +3 -2
  68. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  69. package/dist/lifecycle/publish-npm.js +4 -3
  70. package/dist/lifecycle/release-at.d.ts +40 -0
  71. package/dist/lifecycle/release-at.d.ts.map +1 -0
  72. package/dist/lifecycle/release-at.js +83 -0
  73. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  74. package/dist/lifecycle/release-cut.js +23 -12
  75. package/dist/lifecycle/release-publish.d.ts +28 -12
  76. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  77. package/dist/lifecycle/release-publish.js +29 -17
  78. package/dist/lifecycle/release-store.d.ts +25 -2
  79. package/dist/lifecycle/release-store.d.ts.map +1 -1
  80. package/dist/lifecycle/release-store.js +40 -13
  81. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  82. package/dist/lifecycle/run-ledger.js +1 -0
  83. package/dist/lint/rules/op/index.d.ts +2 -1
  84. package/dist/lint/rules/op/index.d.ts.map +1 -1
  85. package/dist/lint/rules/op/index.js +4 -2
  86. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  87. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  88. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  89. package/dist/op/activities/activity-contracts.d.ts +5 -0
  90. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  91. package/dist/op/activities/activity-contracts.js +3 -0
  92. package/dist/op/activities/shell.d.ts +38 -0
  93. package/dist/op/activities/shell.d.ts.map +1 -1
  94. package/dist/op/activities/shell.js +89 -3
  95. package/dist/op/builders.d.ts +7 -0
  96. package/dist/op/builders.d.ts.map +1 -1
  97. package/dist/op/builders.js +7 -0
  98. package/dist/op/runtime.d.ts +2 -0
  99. package/dist/op/runtime.d.ts.map +1 -1
  100. package/dist/pr-loop.d.ts +14 -3
  101. package/dist/pr-loop.d.ts.map +1 -1
  102. package/dist/pr-loop.js +15 -5
  103. package/dist/pr-report-url.d.ts +2 -2
  104. package/dist/pr-report-url.d.ts.map +1 -1
  105. package/dist/runner-start.d.ts +179 -0
  106. package/dist/runner-start.d.ts.map +1 -0
  107. package/dist/runner-start.js +277 -0
  108. package/dist/wave-runtime.mjs +235 -228
  109. package/dist/workspace/check.schema.json +70 -2
  110. package/dist/workspace/checks/security.d.ts +35 -0
  111. package/dist/workspace/checks/security.d.ts.map +1 -0
  112. package/dist/workspace/checks/security.js +305 -0
  113. package/dist/workspace/checks.d.ts +2 -0
  114. package/dist/workspace/checks.d.ts.map +1 -1
  115. package/dist/workspace/checks.js +5 -0
  116. package/dist/workspace/ci-builds.d.ts +163 -0
  117. package/dist/workspace/ci-builds.d.ts.map +1 -0
  118. package/dist/workspace/ci-builds.js +315 -0
  119. package/dist/workspace/ci-green-cli.d.ts +2 -1
  120. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  121. package/dist/workspace/ci-green-cli.js +25 -5
  122. package/dist/workspace/ci-green-forge.d.ts +12 -0
  123. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  124. package/dist/workspace/ci-green-forge.js +12 -0
  125. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  126. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  127. package/dist/workspace/ci-green-workflow.js +128 -1
  128. package/dist/workspace/ci-green.d.ts +89 -4
  129. package/dist/workspace/ci-green.d.ts.map +1 -1
  130. package/dist/workspace/ci-green.js +310 -23
  131. package/dist/workspace/ci-last-green.schema.json +49 -0
  132. package/dist/workspace/ci-list.schema.json +35 -1
  133. package/dist/workspace/ci-run-index.d.ts +1 -1
  134. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  135. package/dist/workspace/ci-run-index.schema.json +1 -1
  136. package/dist/workspace/ci-run.d.ts +12 -6
  137. package/dist/workspace/ci-run.d.ts.map +1 -1
  138. package/dist/workspace/ci-run.js +72 -16
  139. package/dist/workspace/ci-runner-start.d.ts +77 -0
  140. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  141. package/dist/workspace/ci-runner-start.js +185 -0
  142. package/dist/workspace/ci-serve.d.ts +76 -7
  143. package/dist/workspace/ci-serve.d.ts.map +1 -1
  144. package/dist/workspace/ci-serve.js +153 -19
  145. package/dist/workspace/declaration.d.ts +17 -0
  146. package/dist/workspace/declaration.d.ts.map +1 -1
  147. package/dist/workspace/declaration.js +3 -0
  148. package/dist/workspace/declaration.schema.json +23 -0
  149. package/dist/workspace/ls.d.ts +2 -0
  150. package/dist/workspace/ls.d.ts.map +1 -1
  151. package/dist/workspace/ls.js +1 -1
  152. package/dist/workspace/ls.schema.json +2 -1
  153. package/dist/workspace/member-pipeline.d.ts +1 -0
  154. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  155. package/dist/workspace/member-pipeline.js +2 -0
  156. package/dist/workspace/merge-record.schema.json +49 -0
  157. package/dist/workspace/pr-report.schema.json +3 -3
  158. package/dist/workspace/reason-codes.d.ts +17 -0
  159. package/dist/workspace/reason-codes.d.ts.map +1 -1
  160. package/dist/workspace/reason-codes.js +18 -0
  161. package/dist/workspace/release-candidates.d.ts +129 -0
  162. package/dist/workspace/release-candidates.d.ts.map +1 -0
  163. package/dist/workspace/release-candidates.js +233 -0
  164. package/dist/workspace/release-candidates.schema.json +353 -0
  165. package/dist/workspace/security.d.ts +40 -0
  166. package/dist/workspace/security.d.ts.map +1 -0
  167. package/dist/workspace/security.js +99 -0
  168. package/package.json +8 -1
  169. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  170. package/src/ci-jobs.ts +11 -2
  171. package/src/cli/handlers/build.test.ts +36 -0
  172. package/src/cli/handlers/build.ts +18 -3
  173. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  174. package/src/cli/handlers/lifecycle-health.ts +157 -0
  175. package/src/cli/handlers/lifecycle.ts +1 -1
  176. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  177. package/src/cli/handlers/pr-record.test.ts +201 -0
  178. package/src/cli/handlers/pr.test.ts +22 -0
  179. package/src/cli/handlers/pr.ts +127 -4
  180. package/src/cli/handlers/promote.test.ts +17 -0
  181. package/src/cli/handlers/promote.ts +16 -1
  182. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  183. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  184. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  185. package/src/cli/handlers/release.e2e.test.ts +19 -28
  186. package/src/cli/handlers/release.ts +81 -11
  187. package/src/cli/main.ts +81 -21
  188. package/src/cli/parse-args.ts +17 -1
  189. package/src/cli/registry.ts +18 -4
  190. package/src/components/deploy-inputs.test.ts +108 -0
  191. package/src/components/deploy-inputs.ts +225 -0
  192. package/src/components/driver.ts +11 -0
  193. package/src/components/gate-plan.ts +9 -0
  194. package/src/components/pr-pipeline.ts +56 -15
  195. package/src/components/pr-run.ts +1 -1
  196. package/src/components/release-compile.test.ts +34 -0
  197. package/src/components/release-compile.ts +89 -8
  198. package/src/components/release-promote.test.ts +55 -2
  199. package/src/components/release-promote.ts +93 -23
  200. package/src/components/verbs/bom-writer.ts +1 -1
  201. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  202. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  203. package/src/components/wave-plan.ts +32 -4
  204. package/src/graph-detail.test.ts +57 -0
  205. package/src/graph-detail.ts +33 -7
  206. package/src/health.schema.json +191 -0
  207. package/src/health.test.ts +62 -0
  208. package/src/health.ts +178 -0
  209. package/src/lexicon.ts +41 -1
  210. package/src/lifecycle/merge-record.test.ts +111 -0
  211. package/src/lifecycle/merge-record.ts +154 -0
  212. package/src/lifecycle/publish-attachments.ts +25 -13
  213. package/src/lifecycle/publish-npm.ts +4 -3
  214. package/src/lifecycle/publish-targets.test.ts +34 -4
  215. package/src/lifecycle/release-at.test.ts +48 -0
  216. package/src/lifecycle/release-at.ts +100 -0
  217. package/src/lifecycle/release-cut.test.ts +41 -0
  218. package/src/lifecycle/release-cut.ts +22 -11
  219. package/src/lifecycle/release-publish.test.ts +43 -13
  220. package/src/lifecycle/release-publish.ts +38 -18
  221. package/src/lifecycle/release-store.ts +57 -13
  222. package/src/lifecycle/run-ledger.test.ts +13 -0
  223. package/src/lifecycle/run-ledger.ts +1 -0
  224. package/src/lint/rules/op/index.ts +4 -2
  225. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  226. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  227. package/src/op/activities/activity-contracts.ts +3 -0
  228. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  229. package/src/op/activities/shell.test.ts +44 -1
  230. package/src/op/activities/shell.ts +110 -3
  231. package/src/op/builders.ts +7 -0
  232. package/src/op/runtime.ts +2 -0
  233. package/src/op/step-console.test.ts +4 -2
  234. package/src/pr-loop.test.ts +29 -0
  235. package/src/pr-loop.ts +26 -7
  236. package/src/pr-report-url.ts +2 -2
  237. package/src/runner-start-bundle.test.ts +29 -0
  238. package/src/runner-start.test.ts +179 -0
  239. package/src/runner-start.ts +329 -0
  240. package/src/workspace/check-contract.test.ts +3 -2
  241. package/src/workspace/check.schema.json +70 -2
  242. package/src/workspace/checks/security.test.ts +138 -0
  243. package/src/workspace/checks/security.ts +305 -0
  244. package/src/workspace/checks.ts +5 -0
  245. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  246. package/src/workspace/ci-builds.test.ts +174 -0
  247. package/src/workspace/ci-builds.ts +385 -0
  248. package/src/workspace/ci-green-builds.test.ts +180 -0
  249. package/src/workspace/ci-green-cli.test.ts +54 -0
  250. package/src/workspace/ci-green-cli.ts +26 -5
  251. package/src/workspace/ci-green-forge.ts +24 -0
  252. package/src/workspace/ci-green-workflow.test.ts +84 -1
  253. package/src/workspace/ci-green-workflow.ts +139 -2
  254. package/src/workspace/ci-green.test.ts +5 -2
  255. package/src/workspace/ci-green.ts +359 -27
  256. package/src/workspace/ci-last-green.schema.json +49 -0
  257. package/src/workspace/ci-list.schema.json +35 -1
  258. package/src/workspace/ci-run-index.schema.json +1 -1
  259. package/src/workspace/ci-run-index.ts +1 -1
  260. package/src/workspace/ci-run.test.ts +24 -7
  261. package/src/workspace/ci-run.ts +78 -16
  262. package/src/workspace/ci-runner-start.test.ts +151 -0
  263. package/src/workspace/ci-runner-start.ts +193 -0
  264. package/src/workspace/ci-serve-builds.test.ts +86 -0
  265. package/src/workspace/ci-serve.test.ts +59 -0
  266. package/src/workspace/ci-serve.ts +177 -22
  267. package/src/workspace/declaration.schema.json +23 -0
  268. package/src/workspace/declaration.test.ts +17 -0
  269. package/src/workspace/declaration.ts +22 -1
  270. package/src/workspace/ls.schema.json +2 -1
  271. package/src/workspace/ls.ts +3 -2
  272. package/src/workspace/member-pipeline.ts +2 -1
  273. package/src/workspace/merge-record.schema.json +49 -0
  274. package/src/workspace/pr-report.schema.json +3 -3
  275. package/src/workspace/reason-codes.test.ts +2 -0
  276. package/src/workspace/reason-codes.ts +18 -0
  277. package/src/workspace/release-candidates.schema.json +353 -0
  278. package/src/workspace/release-candidates.test.ts +106 -0
  279. package/src/workspace/release-candidates.ts +315 -0
  280. package/src/workspace/security.test.ts +70 -0
  281. package/src/workspace/security.ts +118 -0
@@ -56,7 +56,10 @@ import { judgeCommits, mergeCommits, readWorkIndex } from "./commit-links.js";
56
56
  import { execFileSync } from "node:child_process";
57
57
  import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError } from "./declaration.js";
58
58
  import { readRunLedger } from "../lifecycle/run-ledger.js";
59
- import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees } from "../lifecycle/git.js";
59
+ import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees, pushLifecycleStatus, writeLedgerFiles } from "../lifecycle/git.js";
60
+ import { ledgerRecordingOff } from "../lifecycle/no-record.js";
61
+ import { agedOut, buildRequestAnnotation, CI_BUILD_TAG_PREFIX, planBuilds, readBuildRequests, readCiRecords, recordOf, recordPath, requestedBuild, sameRecord } from "./ci-builds.js";
62
+ import { dispatchableWorkflows, readWorkflowSources } from "./ci-green-workflow.js";
60
63
  import { signOpRunRecord, verifyOpRunStatement } from "./trust/op-run-statement.js";
61
64
  import { gitTop } from "./tree.js";
62
65
  import { locateWorkspace } from "./which-chant.js";
@@ -79,6 +82,8 @@ export class CiGreenError extends Error {
79
82
  this.name = "CiGreenError";
80
83
  }
81
84
  }
85
+ /** Conclusions that say a run was stopped, or never started, rather than that it failed (#4169). */
86
+ const UNBUILT_CONCLUSIONS = new Set(["cancelled", "stale"]);
82
87
  /** Whether a check run's name matches a pattern: the whole name, `*` standing for any run of characters. */
83
88
  export function matchesCheckRun(pattern, name) {
84
89
  const re = new RegExp(`^${pattern.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`, "s");
@@ -101,6 +106,8 @@ function runVerdict(phase, r) {
101
106
  return "pass";
102
107
  if (r.conclusion === "skipped")
103
108
  return phase.skipped === "pass" ? "pass" : "fail";
109
+ if (r.conclusion !== null && UNBUILT_CONCLUSIONS.has(r.conclusion))
110
+ return "unbuilt";
104
111
  return "fail";
105
112
  }
106
113
  /** Judge one phase against a commit's latest attempts. */
@@ -117,21 +124,34 @@ export function evaluatePhase(phase, latest) {
117
124
  runs.push({ name: r.name, id: r.id, status: r.status, conclusion: r.conclusion, url: r.url, verdict: runVerdict(phase, r), ...(r.opRun ? { opRun: r.opRun } : {}), ...(r.failureCode ? { failureCode: r.failureCode } : {}) });
118
125
  }
119
126
  }
120
- const verdict = runs.some((r) => r.verdict === "fail")
121
- ? "fail"
122
- : runs.some((r) => r.verdict === "pending") || unmatched.length > 0
123
- ? "pending"
124
- : "pass";
125
- return { phase: phase.name, verdict, runs, unmatched };
127
+ return { phase: phase.name, ...phaseVerdictOf(runs, unmatched), runs, unmatched };
128
+ }
129
+ /** A phase's verdict from its runs: a failure wins, then a run still going or a pattern no run matches, then a cancelled run. */
130
+ export function phaseVerdictOf(runs, unmatched) {
131
+ if (runs.some((r) => r.verdict === "fail"))
132
+ return { verdict: "fail" };
133
+ if (runs.some((r) => r.verdict === "pending") || unmatched.length > 0)
134
+ return { verdict: "pending" };
135
+ if (runs.some((r) => r.verdict === "unbuilt"))
136
+ return { verdict: "unbuilt", reason: "cancelled" };
137
+ return { verdict: "pass" };
126
138
  }
127
139
  /** Judge a commit: it fails when a required phase fails, and passes when every one passes. */
128
140
  export function evaluateCommit(green, runs) {
129
141
  const latest = latestAttempts(runs);
130
- const phases = green.require.map((name) => evaluatePhase(green.phases.find((p) => p.name === name), latest));
142
+ return commitVerdictOf(green.require.map((name) => evaluatePhase(green.phases.find((p) => p.name === name), latest)));
143
+ }
144
+ /**
145
+ * A commit's verdict from its required phases: it fails when one fails, is
146
+ * pending while one is, is unbuilt when one is unbuilt (#4169), and passes
147
+ * when every one passes.
148
+ */
149
+ export function commitVerdictOf(phases) {
131
150
  const failed = phases.find((p) => p.verdict === "fail");
132
151
  if (failed)
133
152
  return { verdict: "fail", phases, failure: { phase: failed.phase, run: failed.runs.find((r) => r.verdict === "fail") } };
134
- return { verdict: phases.some((p) => p.verdict === "pending") ? "pending" : "pass", phases, failure: null };
153
+ const verdict = phases.some((p) => p.verdict === "pending") ? "pending" : phases.some((p) => p.verdict === "unbuilt") ? "unbuilt" : "pass";
154
+ return { verdict, phases, failure: null };
135
155
  }
136
156
  /** A time as an annotation and a tick report write it: UTC, to the second. */
137
157
  const iso = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -398,22 +418,31 @@ export async function ciTick(req) {
398
418
  const signer = req.runnerKey !== undefined ? ciTagSigner(top, base, req.runnerKey) : undefined;
399
419
  const links = await commitLinks(req.cwd, top, tracking, commits.map((c) => c.sha));
400
420
  const tags = readCiTags(top, green, base);
421
+ const warnings = [];
401
422
  let runs;
402
- if (green.source === "runs") {
403
- // The runs live on chant/lifecycle, which a CI checkout may not have yet.
404
- const fetched = await fetchLifecycleStatus({ cwd: top });
405
- if (fetched.status === "diverged" || fetched.status === "unreachable") {
406
- throw new Error(`could not fetch chant/lifecycle, where the Op runs are recorded, from ${fetched.remote}: ${fetched.status === "diverged" ? "the local branch and the remote's have diverged" : fetched.stderr}`);
407
- }
408
- runs = await readOpRunSource(req.cwd, top, base, green.signedSince);
423
+ // The runs and the records (#4169) live on chant/lifecycle, which a CI checkout may not have yet.
424
+ const fetched = await fetchLifecycleStatus({ cwd: top });
425
+ const ledgerOk = fetched.status !== "diverged" && fetched.status !== "unreachable";
426
+ if (!ledgerOk) {
427
+ const why = `could not fetch chant/lifecycle from ${fetched.remote}: ${fetched.status === "diverged" ? "the local branch and the remote's have diverged" : fetched.stderr}`;
428
+ if (green.source === "runs")
429
+ throw new Error(why.replace("chant/lifecycle", "chant/lifecycle, where the Op runs are recorded,"));
430
+ warnings.push(`${why}; no _ci records were read or written`);
409
431
  }
432
+ if (green.source === "runs")
433
+ runs = await readOpRunSource(req.cwd, top, base, green.signedSince);
434
+ const runsBy = new Map();
410
435
  const planned = await planTick({
411
436
  green,
412
437
  commits,
413
438
  greenTags: new Set(tags.green.keys()),
414
439
  revokedTags: new Set(tags.revoked.keys()),
415
440
  ignored: tags.ignored,
416
- checkRuns: async (sha) => (runs ? runs.checkRuns(sha) : forge.checkRuns(sha)),
441
+ checkRuns: async (sha) => {
442
+ const got = runs ? runs.checkRuns(sha) : await forge.checkRuns(sha);
443
+ runsBy.set(sha, got);
444
+ return got;
445
+ },
417
446
  now,
418
447
  signer,
419
448
  });
@@ -427,6 +456,10 @@ export async function ciTick(req) {
427
456
  c.ignoredRuns = ignored;
428
457
  }
429
458
  }
459
+ const records = ledgerOk ? readCiRecords(top) : new Map();
460
+ const requests = readBuildRequests(top);
461
+ const retryVia = green.source === "runs" ? "request" : forge?.rerun ? "forge" : null;
462
+ const builds = planBuilds({ green, commits: planned, runs: runsBy, requests, records, now, retryVia });
430
463
  const due = planned.filter((c) => c.made !== null);
431
464
  if (!req.dryRun && due.length > 0) {
432
465
  const ident = taggerConfig(top);
@@ -442,7 +475,217 @@ export async function ciTick(req) {
442
475
  throw new Error(`could not push ${due.map((c) => c.made.tag).join(", ")} to ${remote}: ${cause ? `${cause}\n` : ""}${stderr}`);
443
476
  }
444
477
  }
445
- return { branch: green.branch, window: green.window, ref: tracking, commits: planned, made: due.map((c) => c.made.tag), signer: signer?.principal ?? null, source: green.source };
478
+ const retried = builds.retries.map((r) => ({ sha: r.sha, run: r.run.name }));
479
+ let recorded = [];
480
+ if (!req.dryRun) {
481
+ const asked = await askForBuilds({ top, remote, green, forge, builds, requests, records, planned, warnings });
482
+ recorded = ledgerOk && !ledgerRecordingOff() ? await writeRecords({ top, green, planned, records, builds, requests: asked.live, dispatched: asked.dispatched, now, warnings }) : [];
483
+ }
484
+ return {
485
+ branch: green.branch,
486
+ window: green.window,
487
+ ref: tracking,
488
+ commits: planned,
489
+ made: due.map((c) => c.made.tag),
490
+ requested: builds.requests,
491
+ retried,
492
+ recorded,
493
+ warnings,
494
+ signer: signer?.principal ?? null,
495
+ source: green.source,
496
+ };
497
+ }
498
+ /**
499
+ * Make what the plan asks for (#4169): push each new request tag and start
500
+ * the workflows holding its phases at it on GitHub, start any older request
501
+ * no tick has started yet, run the failed jobs again, and delete the tags of
502
+ * requests that are done. Each step that fails is a warning, never the
503
+ * tick's failure: the green tags are already pushed.
504
+ */
505
+ async function askForBuilds(input) {
506
+ const { top, remote, green, forge, builds, warnings } = input;
507
+ const live = new Map(input.requests);
508
+ for (const tag of builds.resolved)
509
+ live.delete(tag.slice(CI_BUILD_TAG_PREFIX.length));
510
+ if (builds.requests.length > 0) {
511
+ const ident = taggerConfig(top);
512
+ const made = [];
513
+ for (const r of builds.requests) {
514
+ if (tryGit(top, [...ident, "tag", "-a", "-m", buildRequestAnnotation(r), r.tag, r.commit]) === null)
515
+ warnings.push(`could not make ${r.tag}`);
516
+ else
517
+ made.push(r);
518
+ }
519
+ if (made.length > 0) {
520
+ try {
521
+ git(top, ["push", "--quiet", remote, ...made.map((r) => `refs/tags/${r.tag}`)]);
522
+ for (const r of made)
523
+ live.set(r.commit, r);
524
+ }
525
+ catch (err) {
526
+ for (const r of made)
527
+ tryGit(top, ["tag", "-d", r.tag]);
528
+ const stderr = stderrOf(err);
529
+ warnings.push(`could not push ${made.map((r) => r.tag).join(", ")} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
530
+ }
531
+ }
532
+ }
533
+ const dispatched = new Set();
534
+ if (green.source === "checks" && forge?.dispatch) {
535
+ const sources = readWorkflowSources(top, "");
536
+ for (const r of live.values()) {
537
+ if (input.records.get(r.commit)?.request?.dispatched && input.records.get(r.commit)?.request?.tag === r.tag)
538
+ continue;
539
+ // A run already going at the commit was started for it, by the tick before or by `ci run --at`.
540
+ const c = input.planned.find((x) => x.sha === r.commit);
541
+ if (c?.verdict?.phases.some((p) => r.phases.includes(p.phase) && p.runs.some((x) => x.status !== "completed"))) {
542
+ dispatched.add(r.commit);
543
+ continue;
544
+ }
545
+ const { dispatchable, without } = dispatchableWorkflows(green, r.phases, sources);
546
+ for (const w of without)
547
+ warnings.push(`${w} holds a run of ${r.phases.join(", ")} and has no workflow_dispatch trigger, so ${r.tag} can't start it; regenerate it with chant ci workflow`);
548
+ let ok = dispatchable.length > 0;
549
+ for (const w of dispatchable) {
550
+ try {
551
+ await forge.dispatch(w, r.tag);
552
+ }
553
+ catch (err) {
554
+ ok = false;
555
+ warnings.push(`could not start ${w} at ${r.tag}: ${err instanceof Error ? err.message : String(err)}`);
556
+ }
557
+ }
558
+ if (ok)
559
+ dispatched.add(r.commit);
560
+ }
561
+ }
562
+ if (forge?.rerun) {
563
+ for (const r of builds.retries) {
564
+ try {
565
+ await forge.rerun(r.run);
566
+ }
567
+ catch (err) {
568
+ warnings.push(`could not run ${r.run.name} at ${r.sha.slice(0, 12)} again, so its failure counts once the retry's hour is up: ${err instanceof Error ? err.message : String(err)}`);
569
+ }
570
+ }
571
+ }
572
+ if (builds.resolved.length > 0) {
573
+ for (const tag of builds.resolved)
574
+ tryGit(top, ["tag", "-d", tag]);
575
+ if (tryGit(top, ["push", "--quiet", remote, ...builds.resolved.map((t) => `:refs/tags/${t}`)]) === null) {
576
+ warnings.push(`could not delete ${builds.resolved.join(", ")} on ${remote}; a done request asks for nothing, so they can stay`);
577
+ }
578
+ }
579
+ return { live, dispatched };
580
+ }
581
+ /**
582
+ * Write each judged commit's record, and rewrite a record whose commit left
583
+ * the window still pending as unbuilt (#4169), in one commit on
584
+ * `chant/lifecycle`, and push it. Only records whose content changed are
585
+ * written. A push another writer beat is tried once more on the new tip.
586
+ */
587
+ async function writeRecords(input) {
588
+ const { top, green, planned, records, builds, requests, now, warnings } = input;
589
+ const writes = [];
590
+ const inWindow = new Set(planned.map((c) => c.sha));
591
+ for (const c of planned) {
592
+ const previous = records.get(c.sha);
593
+ let record = recordOf({ green, commit: c, previous, retries: builds.retries, requests, now });
594
+ if (record.request) {
595
+ const started = input.dispatched.has(c.sha) || (previous?.request?.tag === record.request.tag && previous.request.dispatched === true);
596
+ if (started)
597
+ record = { ...record, request: { ...record.request, dispatched: true } };
598
+ }
599
+ if (!sameRecord(previous, record))
600
+ writes.push({ path: recordPath(c.sha), content: `${JSON.stringify(record, null, 2)}\n` });
601
+ }
602
+ for (const [sha, r] of records) {
603
+ if (inWindow.has(sha) || r.state !== "pending")
604
+ continue;
605
+ writes.push({ path: recordPath(sha), content: `${JSON.stringify(agedOut(r, now), null, 2)}\n` });
606
+ }
607
+ if (writes.length === 0)
608
+ return [];
609
+ for (let attempt = 0; attempt < 2; attempt++) {
610
+ try {
611
+ await writeLedgerFiles(writes, `ci: ${writes.length} commit record${writes.length === 1 ? "" : "s"}`, { cwd: top });
612
+ }
613
+ catch (err) {
614
+ warnings.push(`could not write the _ci records: ${err instanceof Error ? err.message : String(err)}`);
615
+ return [];
616
+ }
617
+ const pushed = await pushLifecycleStatus({ cwd: top });
618
+ if (pushed.status === "pushed" || pushed.status === "no-remote" || pushed.status === "not-recorded")
619
+ return writes.map((w) => w.path);
620
+ if (pushed.status !== "stale" || attempt === 1) {
621
+ warnings.push(`could not push the _ci records: ${pushed.stderr}`);
622
+ return [];
623
+ }
624
+ // Another writer pushed first: drop this commit, follow the remote, and write again on its tip.
625
+ tryGit(top, ["update-ref", "refs/heads/chant/lifecycle", "refs/heads/chant/lifecycle^"]);
626
+ const again = await fetchLifecycleStatus({ cwd: top });
627
+ if (again.status === "diverged" || again.status === "unreachable") {
628
+ warnings.push(`could not push the _ci records: chant/lifecycle moved on the remote and could not be fetched again`);
629
+ return [];
630
+ }
631
+ }
632
+ return [];
633
+ }
634
+ /**
635
+ * `chant ci run --at <sha>`: ask for every required phase to be built at a
636
+ * commit of the branch, by pushing `ci/build/<sha>`. Given a forge that can
637
+ * start workflows, and with the source check runs, it starts the workflows
638
+ * holding the phases at the tag now; otherwise the next tick does, or `ci
639
+ * serve --build-command` for chant runs. Once the phases pass, the tick tags
640
+ * the commit green like any other, so it can be cut.
641
+ */
642
+ export async function requestBuild(req) {
643
+ const read = readGreen(req.cwd);
644
+ const { green, top } = read;
645
+ const remote = req.remote ?? "origin";
646
+ const tracking = `refs/remotes/${remote}/${green.branch}`;
647
+ try {
648
+ git(top, ["fetch", "--quiet", "--no-tags", remote, `+refs/heads/${green.branch}:${tracking}`, `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
649
+ }
650
+ catch (err) {
651
+ throw new Error(`could not fetch ${green.branch} and the build requests from ${remote}: ${stderrOf(err)}`);
652
+ }
653
+ if (req.at.startsWith("-") || tryGit(top, ["rev-parse", "--verify", "--quiet", `${req.at}^{commit}`]) === null)
654
+ throw new WorkspaceReadError("revision-unknown", `--at ${req.at} names no commit in this repository`);
655
+ const sha = git(top, ["rev-parse", `${req.at}^{commit}`]);
656
+ const onBranch = git(top, ["rev-list", "--first-parent", tracking]).split("\n").includes(sha);
657
+ if (!onBranch)
658
+ throw new Error(`${sha.slice(0, 12)} is not a first-parent commit of ${remote}/${green.branch}, and only those are judged green`);
659
+ const existing = readBuildRequests(top).get(sha);
660
+ const request = existing ?? requestedBuild(green, sha, green.require, req.now ?? new Date());
661
+ const warnings = [];
662
+ if (!existing) {
663
+ git(top, [...taggerConfig(top), "tag", "-a", "-m", buildRequestAnnotation(request), request.tag, sha]);
664
+ try {
665
+ git(top, ["push", "--quiet", remote, `refs/tags/${request.tag}`]);
666
+ }
667
+ catch (err) {
668
+ tryGit(top, ["tag", "-d", request.tag]);
669
+ const stderr = stderrOf(err);
670
+ throw new Error(`could not push ${request.tag} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
671
+ }
672
+ }
673
+ const dispatched = [];
674
+ if (green.source === "checks" && req.forge?.dispatch) {
675
+ const { dispatchable, without } = dispatchableWorkflows(green, request.phases, readWorkflowSources(top, ""));
676
+ for (const w of without)
677
+ warnings.push(`${w} holds a run of a required phase and has no workflow_dispatch trigger, so it can't be started at ${request.tag}`);
678
+ for (const w of dispatchable) {
679
+ try {
680
+ await req.forge.dispatch(w, request.tag);
681
+ dispatched.push(w);
682
+ }
683
+ catch (err) {
684
+ warnings.push(`could not start ${w} at ${request.tag}: ${err instanceof Error ? err.message : String(err)}; the next tick tries again`);
685
+ }
686
+ }
687
+ }
688
+ return { request, existed: existing !== undefined, dispatched, warnings };
446
689
  }
447
690
  const RUN_LEDGER_FILE = /^runs__(.+)\.jsonl$/;
448
691
  /**
@@ -589,8 +832,17 @@ export function formatTick(r, dryRun = false) {
589
832
  }
590
833
  else {
591
834
  const v = c.verdict;
592
- const waiting = v.phases.filter((p) => p.verdict === "pending").map((p) => p.phase);
593
- const what = c.before === "green" ? "green" : v.verdict === "fail" ? `failed: phase ${v.failure.phase}, ${v.failure.run.name} ${v.failure.run.conclusion}` : `undecided: ${waiting.join(", ")}`;
835
+ const why = (p) => `${p.phase}${p.reason ? ` (${p.reason}${p.coveredBy ? `, covered by ${p.coveredBy.slice(0, 12)}` : ""})` : ""}`;
836
+ const waiting = v.phases.filter((p) => p.verdict === "pending").map(why);
837
+ const unbuilt = v.phases.filter((p) => p.verdict === "unbuilt").map(why);
838
+ const culprit = v.phases.find((p) => p.culprit)?.culprit;
839
+ const what = c.before === "green"
840
+ ? "green"
841
+ : v.verdict === "fail"
842
+ ? `failed: phase ${v.failure.phase}, ${v.failure.run.name} ${v.failure.run.conclusion}${culprit ? `; culprit ${culprit.slice(0, 12)}` : ""}`
843
+ : v.verdict === "unbuilt"
844
+ ? `unbuilt: ${unbuilt.join(", ")}`
845
+ : `undecided: ${waiting.join(", ")}`;
594
846
  lines.push(` ${short} ${what}`);
595
847
  }
596
848
  const why = whyWorkUnknown(c.work);
@@ -605,6 +857,14 @@ export function formatTick(r, dryRun = false) {
605
857
  }
606
858
  if (r.signer && r.made.length > 0)
607
859
  lines.push(`signed as ${r.signer}`);
860
+ for (const q of r.requested)
861
+ lines.push(`${dryRun ? "would ask" : "asked"} for ${q.phases.join(", ")} at ${q.commit.slice(0, 12)} (${q.reason}): ${q.tag}`);
862
+ for (const x of r.retried)
863
+ lines.push(`${dryRun ? "would run" : "ran"} ${x.run} at ${x.sha.slice(0, 12)} again`);
864
+ if (r.recorded.length > 0)
865
+ lines.push(`recorded ${r.recorded.length} commit${r.recorded.length === 1 ? "" : "s"} on chant/lifecycle`);
866
+ for (const w of r.warnings)
867
+ lines.push(`warning: ${w}`);
608
868
  return lines.join("\n");
609
869
  }
610
870
  // ── last-green ───────────────────────────────────────────────────────────────
@@ -616,6 +876,8 @@ function branchRefOf(top, green, remote = "origin") {
616
876
  throw new CiGreenError("ci-branch-unknown", `this repository has neither ${candidates.join(" nor ")}`);
617
877
  return ref;
618
878
  }
879
+ /** How many commits above the green one `ci last-green` reports with their records. */
880
+ export const LAST_GREEN_NEWER = 50;
619
881
  /**
620
882
  * `chant ci last-green`: the newest first-parent commit of the branch with a
621
883
  * green tag and no revoked tag. It reads local refs and tags and never
@@ -637,16 +899,21 @@ export function lastGreen(req) {
637
899
  const tags = readCiTags(top, green, git(top, ["rev-parse", `${branchRef}^{commit}`]));
638
900
  let commit = null;
639
901
  const ignored = [];
640
- if (tags.green.size > 0 || tags.ignored.size > 0) {
902
+ const newer = [];
903
+ const records = readCiRecords(top);
904
+ if (tags.green.size > 0 || tags.ignored.size > 0 || records.size > 0) {
641
905
  for (const sha of git(top, ["rev-list", "--first-parent", ref]).split("\n")) {
642
906
  ignored.push(...(tags.ignored.get(sha) ?? []));
643
- if (!tags.green.has(sha) || tags.revoked.has(sha))
907
+ if (!tags.green.has(sha) || tags.revoked.has(sha)) {
908
+ if (newer.length < LAST_GREEN_NEWER)
909
+ newer.push({ sha, record: recordSummary(green, records.get(sha)) });
644
910
  continue;
911
+ }
645
912
  commit = { sha, tag: `${CI_GREEN_TAG_PREFIX}${sha}`, tagged: tags.green.get(sha).tagged, committed: git(top, ["show", "-s", "--format=%cI", sha]) };
646
913
  break;
647
914
  }
648
915
  }
649
- return { ...head, workspace, branch: green.branch, ref, commit, ignored };
916
+ return { ...head, workspace, branch: green.branch, ref, commit, ignored, newer: commit ? newer : [] };
650
917
  }
651
918
  catch (err) {
652
919
  if (err instanceof CiGreenError || err instanceof WorkspaceReadError)
@@ -657,6 +924,17 @@ export function lastGreen(req) {
657
924
  // ── list ─────────────────────────────────────────────────────────────────────
658
925
  export const CI_LIST_CONTRACT_VERSION = 1;
659
926
  export const CI_LIST_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/ci-list/v1/ci-list.schema.json";
927
+ /** The summary of a record, from its required phases in `require` order. */
928
+ export function recordSummary(green, r) {
929
+ if (!r)
930
+ return null;
931
+ const phases = green.require.map((name) => r.phases[name]).filter((p) => p !== undefined);
932
+ const first = (k) => phases.find((p) => p[k] !== undefined)?.[k];
933
+ const reason = first("reason");
934
+ const coveredBy = first("coveredBy");
935
+ const culprit = first("culprit");
936
+ return { state: r.state, ...(reason ? { reason } : {}), ...(coveredBy ? { coveredBy } : {}), ...(culprit ? { culprit } : {}) };
937
+ }
660
938
  const asRun = (r) => {
661
939
  if (!r || typeof r !== "object")
662
940
  return null;
@@ -722,6 +1000,7 @@ export async function ciList(req) {
722
1000
  if (source === "runs" && req.forge)
723
1001
  throw new Error("--forge reads check runs, and the source is chant runs (ci.green.source or --source runs); drop one");
724
1002
  const runs = source === "runs" ? await readOpRunSource(req.cwd, top, base, green.signedSince) : undefined;
1003
+ const records = readCiRecords(top);
725
1004
  const out = [];
726
1005
  for (const c of commits) {
727
1006
  const g = tags.green.get(c.sha);
@@ -747,6 +1026,7 @@ export async function ciList(req) {
747
1026
  work: links?.get(c.sha) ?? null,
748
1027
  ignored: tags.ignored.get(c.sha) ?? [],
749
1028
  ...(runs ? { ignoredRuns: runs.ignored.get(c.sha) ?? [] } : {}),
1029
+ record: recordSummary(green, records.get(c.sha)),
750
1030
  });
751
1031
  }
752
1032
  return { ...head, workspace, branch: green.branch, ref, window: req.since === undefined ? green.window : null, since: req.since ?? null, require: [...green.require], commits: out };
@@ -757,6 +1037,11 @@ export async function ciList(req) {
757
1037
  throw err;
758
1038
  }
759
1039
  }
1040
+ /** A record summary in words, for `ci list` and the tick. */
1041
+ export function describeRecord(r) {
1042
+ const parts = [r.state, ...(r.reason ? [r.reason] : []), ...(r.coveredBy ? [`covered by ${r.coveredBy.slice(0, 12)}`] : []), ...(r.culprit ? [`culprit ${r.culprit.slice(0, 12)}`] : [])];
1043
+ return parts.join(", ");
1044
+ }
760
1045
  /** The text `chant ci list` prints: one line per commit. */
761
1046
  export function formatList(doc) {
762
1047
  const lines = [`${doc.branch}, ${doc.since ? `commits after ${doc.since}` : `commits within ${doc.window}`}: ${doc.commits.length}`];
@@ -764,6 +1049,8 @@ export function formatList(doc) {
764
1049
  const phases = c.phases.map((p) => `${p.phase}=${p.result}`).join(" ");
765
1050
  const why = whyWorkUnknown(c.work);
766
1051
  lines.push(` ${c.sha.slice(0, 12)} ${c.state.padEnd(9)} ${phases}${why ? `; why unknown: ${why}` : ""}`);
1052
+ if (c.record && c.state === "undecided")
1053
+ lines.push(` recorded ${describeRecord(c.record)}`);
767
1054
  for (const t of c.ignored)
768
1055
  lines.push(` ignored ${t.tag} (${t.status}): ${t.reason}`);
769
1056
  for (const r of c.ignoredRuns ?? [])
@@ -104,6 +104,55 @@
104
104
  }
105
105
  ]
106
106
  },
107
+ "newer": {
108
+ "type": "array",
109
+ "description": "Added within contract version 1 by #4169, written by chant 0.124.33 and newer. The first-parent commits walked past, newest first and above the one found, each with what its _ci/<sha>.json record on the local chant/lifecycle says (null when it has none), so a reader can say why the newest commits are not green. At most 50.",
110
+ "items": {
111
+ "type": "object",
112
+ "required": [
113
+ "sha",
114
+ "record"
115
+ ],
116
+ "properties": {
117
+ "sha": {
118
+ "type": "string",
119
+ "pattern": "^[0-9a-f]{40,64}$"
120
+ },
121
+ "record": {
122
+ "oneOf": [
123
+ {
124
+ "type": "null"
125
+ },
126
+ {
127
+ "type": "object",
128
+ "required": [
129
+ "state"
130
+ ],
131
+ "properties": {
132
+ "state": {
133
+ "enum": [
134
+ "green",
135
+ "red",
136
+ "unbuilt",
137
+ "pending"
138
+ ]
139
+ },
140
+ "reason": {
141
+ "type": "string"
142
+ },
143
+ "coveredBy": {
144
+ "type": "string"
145
+ },
146
+ "culprit": {
147
+ "type": "string"
148
+ }
149
+ }
150
+ }
151
+ ]
152
+ }
153
+ }
154
+ }
155
+ },
107
156
  "ignored": {
108
157
  "type": "array",
109
158
  "description": "Added within contract version 1 by #4031, written by chant 0.124.28 and newer. The tags on the commits walked, newest first and down to the one found, that do not count for want of a valid statement. Empty when ci.green.signedSince is not set.",
@@ -284,9 +284,10 @@
284
284
  "pass",
285
285
  "fail",
286
286
  "pending",
287
+ "unbuilt",
287
288
  "unrecorded"
288
289
  ],
289
- "description": "pass and fail come from the tags (a green tag records the phases that passed, a revoked tag the one that failed) or, for an undecided commit, from the forge under --forge, or from the signed Op runs on the local chant/lifecycle when the source is chant runs (#4044). pending is such a read with runs still going or not yet reported. unrecorded means no tag holds the result and nothing was read."
290
+ "description": "pass and fail come from the tags (a green tag records the phases that passed, a revoked tag the one that failed) or, for an undecided commit, from the forge under --forge, or from the signed Op runs on the local chant/lifecycle when the source is chant runs (#4044). pending is such a read with runs still going or not yet reported. unbuilt (#4169, chant 0.124.33 and newer) is such a read whose runs were cancelled. unrecorded means no tag holds the result and nothing was read."
290
291
  },
291
292
  "runs": {
292
293
  "type": "array",
@@ -406,6 +407,39 @@
406
407
  }
407
408
  ]
408
409
  },
410
+ "record": {
411
+ "description": "Added within contract version 1 by #4169, written by chant 0.124.33 and newer. What the commit's _ci/<sha>.json record on the local chant/lifecycle says, the tick's last word on it, or null when it has none. state is green, red, unbuilt or pending. reason, coveredBy and culprit come from the required phases: why a phase is unbuilt or still pending (cancelled, superseded, retrying, requested, request-unserved or aged-out), the newer commit whose run covers a superseded phase, and the commit a bisect found broke a phase. Each is absent when no phase has one.",
412
+ "oneOf": [
413
+ {
414
+ "type": "null"
415
+ },
416
+ {
417
+ "type": "object",
418
+ "required": [
419
+ "state"
420
+ ],
421
+ "properties": {
422
+ "state": {
423
+ "enum": [
424
+ "green",
425
+ "red",
426
+ "unbuilt",
427
+ "pending"
428
+ ]
429
+ },
430
+ "reason": {
431
+ "type": "string"
432
+ },
433
+ "coveredBy": {
434
+ "type": "string"
435
+ },
436
+ "culprit": {
437
+ "type": "string"
438
+ }
439
+ }
440
+ }
441
+ ]
442
+ },
409
443
  "ignored": {
410
444
  "type": "array",
411
445
  "description": "Added within contract version 1 by #4031, written by chant 0.124.28 and newer. The commit's ci tags that do not count for want of a valid statement signed by a runner key. A tag on any commit that is not a strict first-parent ancestor of ci.green.signedSince needs one; empty when signedSince is not set. state, green and revoked count only the tags that are not ignored.",
@@ -38,7 +38,7 @@ export declare const CI_RUN_INDEX_KEY = "runs/index.json";
38
38
  export declare const CI_RUN_INDEX_LIMIT = 200;
39
39
  /** One job the runner ran. */
40
40
  export interface CiRunIndexEntry {
41
- job: "plan" | "apply";
41
+ job: "plan" | "apply" | "record";
42
42
  /** The pull request, or null when the job found none. */
43
43
  pr: number | null;
44
44
  commit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ci-run-index.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-run-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAwC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAG3H,eAAO,MAAM,sBAAsB,0FAA0F,CAAC;AAC9H,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAC7C,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAClD,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACtB,yDAAyD;IACzD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,qEAAqE;IACrE,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,sBAAsB,CAAC;IACvC,QAAQ,EAAE,OAAO,qBAAqB,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,IAAI,EAAE,eAAe,EAAE,CAAC;CACzB;AAID,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAc7G;AAED,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAEtH;AAKD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB;AAED,gHAAgH;AAChH,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,SAA6B,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAQzH;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAS1D;AAID,iEAAiE;AACjE,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAepE"}
1
+ {"version":3,"file":"ci-run-index.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-run-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAwC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAG3H,eAAO,MAAM,sBAAsB,0FAA0F,CAAC;AAC9H,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAC7C,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAClD,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACjC,yDAAyD;IACzD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,qEAAqE;IACrE,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,sBAAsB,CAAC;IACvC,QAAQ,EAAE,OAAO,qBAAqB,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,IAAI,EAAE,eAAe,EAAE,CAAC;CACzB;AAID,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAc7G;AAED,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAEtH;AAKD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB;AAED,gHAAgH;AAChH,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,SAA6B,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAQzH;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAS1D;AAID,iEAAiE;AACjE,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAepE"}
@@ -23,7 +23,7 @@
23
23
  "if": { "properties": { "status": { "const": "fail" } } },
24
24
  "then": { "required": ["failure"], "properties": { "failure": { "type": "object" } } },
25
25
  "properties": {
26
- "job": { "enum": ["plan", "apply"] },
26
+ "job": { "enum": ["plan", "apply", "record"], "description": "record (#4168, chant 0.124.33 and newer): the merge record job." },
27
27
  "pr": { "type": ["integer", "null"], "minimum": 1, "description": "The pull or merge request number: the one the job's report names, else --pr. null when neither has one." },
28
28
  "commit": { "type": "string", "pattern": "^[0-9a-f]{40,64}$", "description": "The full commit the job ran at." },
29
29
  "status": { "enum": ["ok", "fail"] },