@intentius/chant 0.124.40 → 0.124.41

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/__fixtures__/oidc-emulator.d.ts +49 -0
  2. package/dist/__fixtures__/oidc-emulator.d.ts.map +1 -0
  3. package/dist/__fixtures__/oidc-emulator.js +89 -0
  4. package/dist/ci-handover.d.ts +117 -0
  5. package/dist/ci-handover.d.ts.map +1 -0
  6. package/dist/ci-handover.js +217 -0
  7. package/dist/ci-identity.d.ts +190 -0
  8. package/dist/ci-identity.d.ts.map +1 -0
  9. package/dist/ci-identity.js +219 -0
  10. package/dist/cli/handlers/approve.d.ts +2 -1
  11. package/dist/cli/handlers/approve.d.ts.map +1 -1
  12. package/dist/cli/handlers/approve.js +20 -6
  13. package/dist/cli/handlers/build.d.ts.map +1 -1
  14. package/dist/cli/handlers/build.js +15 -1
  15. package/dist/cli/handlers/fan-out.js +16 -3
  16. package/dist/cli/handlers/gate-resume.d.ts +3 -0
  17. package/dist/cli/handlers/gate-resume.d.ts.map +1 -1
  18. package/dist/cli/handlers/gate-resume.js +15 -2
  19. package/dist/cli/handlers/lock.d.ts +8 -0
  20. package/dist/cli/handlers/lock.d.ts.map +1 -0
  21. package/dist/cli/handlers/lock.js +197 -0
  22. package/dist/cli/handlers/pr-env.d.ts +34 -0
  23. package/dist/cli/handlers/pr-env.d.ts.map +1 -0
  24. package/dist/cli/handlers/pr-env.js +436 -0
  25. package/dist/cli/handlers/pr.d.ts +74 -0
  26. package/dist/cli/handlers/pr.d.ts.map +1 -1
  27. package/dist/cli/handlers/pr.js +503 -53
  28. package/dist/cli/handlers/promote.d.ts.map +1 -1
  29. package/dist/cli/handlers/promote.js +9 -0
  30. package/dist/cli/handlers/query.d.ts +6 -0
  31. package/dist/cli/handlers/query.d.ts.map +1 -0
  32. package/dist/cli/handlers/query.js +80 -0
  33. package/dist/cli/main.d.ts.map +1 -1
  34. package/dist/cli/main.js +41 -2
  35. package/dist/cli/parse-args.d.ts.map +1 -1
  36. package/dist/cli/parse-args.js +31 -2
  37. package/dist/cli/registry.d.ts +12 -2
  38. package/dist/cli/registry.d.ts.map +1 -1
  39. package/dist/components/fan-out-run.d.ts +28 -0
  40. package/dist/components/fan-out-run.d.ts.map +1 -1
  41. package/dist/components/fan-out-run.js +27 -3
  42. package/dist/components/pr-environment.d.ts +298 -0
  43. package/dist/components/pr-environment.d.ts.map +1 -0
  44. package/dist/components/pr-environment.js +406 -0
  45. package/dist/components/pr-hooks.d.ts +66 -0
  46. package/dist/components/pr-hooks.d.ts.map +1 -0
  47. package/dist/components/pr-hooks.js +119 -0
  48. package/dist/components/pr-pipeline.d.ts +72 -2
  49. package/dist/components/pr-pipeline.d.ts.map +1 -1
  50. package/dist/components/pr-pipeline.js +96 -5
  51. package/dist/components/pr-policy.d.ts +109 -0
  52. package/dist/components/pr-policy.d.ts.map +1 -0
  53. package/dist/components/pr-policy.js +150 -0
  54. package/dist/components/pr-run.d.ts.map +1 -1
  55. package/dist/components/pr-run.js +5 -3
  56. package/dist/gate-decision.d.ts +15 -2
  57. package/dist/gate-decision.d.ts.map +1 -1
  58. package/dist/gate-decision.js +17 -2
  59. package/dist/gate-policy.d.ts +140 -0
  60. package/dist/gate-policy.d.ts.map +1 -0
  61. package/dist/gate-policy.js +242 -0
  62. package/dist/lifecycle/gate-checkpoint.d.ts +1 -1
  63. package/dist/lifecycle/gate-checkpoint.js +1 -1
  64. package/dist/{workspace/trust → lifecycle}/gate-seal.d.ts +1 -1
  65. package/dist/lifecycle/gate-seal.d.ts.map +1 -0
  66. package/dist/lifecycle/query.d.ts +68 -0
  67. package/dist/lifecycle/query.d.ts.map +1 -0
  68. package/dist/lifecycle/query.js +246 -0
  69. package/dist/lifecycle/run-ledger.d.ts +13 -0
  70. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  71. package/dist/lifecycle/run-ledger.js +16 -0
  72. package/dist/lifecycle/target-lock.d.ts +97 -0
  73. package/dist/lifecycle/target-lock.d.ts.map +1 -0
  74. package/dist/lifecycle/target-lock.js +286 -0
  75. package/dist/op/activities/activity-contracts.d.ts +30 -0
  76. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  77. package/dist/op/activities/activity-contracts.js +26 -0
  78. package/dist/op/activities/index.d.ts +2 -0
  79. package/dist/op/activities/index.d.ts.map +1 -1
  80. package/dist/op/activities/index.js +4 -0
  81. package/dist/op/activities/review-agent.d.ts +137 -0
  82. package/dist/op/activities/review-agent.d.ts.map +1 -0
  83. package/dist/op/activities/review-agent.js +266 -0
  84. package/dist/op/builders.d.ts +9 -0
  85. package/dist/op/builders.d.ts.map +1 -1
  86. package/dist/op/builders.js +11 -0
  87. package/dist/op/cron.d.ts +8 -0
  88. package/dist/op/cron.d.ts.map +1 -1
  89. package/dist/op/cron.js +51 -0
  90. package/dist/op/gate-approval.d.ts +20 -13
  91. package/dist/op/gate-approval.d.ts.map +1 -1
  92. package/dist/op/gate-approval.js +13 -18
  93. package/dist/op/gate-quorum.d.ts +131 -0
  94. package/dist/op/gate-quorum.d.ts.map +1 -0
  95. package/dist/op/gate-quorum.js +183 -0
  96. package/dist/op/gate-resume.d.ts +4 -2
  97. package/dist/op/gate-resume.d.ts.map +1 -1
  98. package/dist/op/gate-resume.js +5 -4
  99. package/dist/op/gate-review.js +1 -1
  100. package/dist/op/gate.d.ts +32 -4
  101. package/dist/op/gate.d.ts.map +1 -1
  102. package/dist/op/gate.js +40 -24
  103. package/dist/op/index.d.ts +6 -4
  104. package/dist/op/index.d.ts.map +1 -1
  105. package/dist/op/index.js +4 -3
  106. package/dist/op/local-executor.d.ts.map +1 -1
  107. package/dist/op/local-executor.js +9 -3
  108. package/dist/op/op-waves-run.d.ts +62 -1
  109. package/dist/op/op-waves-run.d.ts.map +1 -1
  110. package/dist/op/op-waves-run.js +190 -11
  111. package/dist/op/op-waves.d.ts +55 -3
  112. package/dist/op/op-waves.d.ts.map +1 -1
  113. package/dist/op/op-waves.js +63 -8
  114. package/dist/op/schedule-overdue.d.ts +57 -0
  115. package/dist/op/schedule-overdue.d.ts.map +1 -0
  116. package/dist/op/schedule-overdue.js +138 -0
  117. package/dist/override-gate.d.ts +2 -2
  118. package/dist/override-gate.d.ts.map +1 -1
  119. package/dist/override-gate.js +1 -1
  120. package/dist/pr-forge.d.ts +36 -0
  121. package/dist/pr-forge.d.ts.map +1 -1
  122. package/dist/pr-forge.js +82 -0
  123. package/dist/pr-loop.d.ts +90 -1
  124. package/dist/pr-loop.d.ts.map +1 -1
  125. package/dist/pr-loop.js +77 -3
  126. package/dist/pr-post.d.ts +72 -0
  127. package/dist/pr-post.d.ts.map +1 -0
  128. package/dist/pr-post.js +134 -0
  129. package/dist/wave-blocked.d.ts +79 -0
  130. package/dist/wave-blocked.d.ts.map +1 -0
  131. package/dist/wave-blocked.js +245 -0
  132. package/dist/wave-engine.d.ts +6 -0
  133. package/dist/wave-engine.d.ts.map +1 -1
  134. package/dist/wave-engine.js +2 -0
  135. package/dist/wave-runtime.mjs +242 -242
  136. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +1 -0
  137. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -1
  138. package/dist/workspace/__fixtures__/two-member-workspace.js +1 -1
  139. package/dist/workspace/ci-branches.d.ts +51 -0
  140. package/dist/workspace/ci-branches.d.ts.map +1 -0
  141. package/dist/workspace/ci-branches.js +69 -0
  142. package/dist/workspace/ci-cloud-identity.d.ts +11 -0
  143. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -0
  144. package/dist/workspace/ci-cloud-identity.js +24 -0
  145. package/dist/workspace/ci-locks.d.ts +12 -0
  146. package/dist/workspace/ci-locks.d.ts.map +1 -0
  147. package/dist/workspace/ci-locks.js +6 -0
  148. package/dist/workspace/ci-run.d.ts +17 -1
  149. package/dist/workspace/ci-run.d.ts.map +1 -1
  150. package/dist/workspace/ci-run.js +64 -3
  151. package/dist/workspace/declaration.d.ts +63 -0
  152. package/dist/workspace/declaration.d.ts.map +1 -1
  153. package/dist/workspace/declaration.js +72 -1
  154. package/dist/workspace/declaration.schema.json +147 -0
  155. package/dist/workspace/identity-gate-rule.d.ts +1 -1
  156. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  157. package/dist/workspace/identity-gate-rule.js +1 -1
  158. package/dist/workspace/identity.d.ts +1 -1
  159. package/dist/workspace/identity.d.ts.map +1 -1
  160. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  161. package/dist/workspace/lineage-upgrade.js +8 -5
  162. package/dist/workspace/member-pipeline.d.ts +6 -0
  163. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  164. package/dist/workspace/member-pipeline.js +23 -0
  165. package/dist/workspace/pr-report.schema.json +73 -2
  166. package/dist/workspace/security.d.ts.map +1 -1
  167. package/dist/workspace/security.js +4 -0
  168. package/dist/workspace/status-gates.d.ts +3 -2
  169. package/dist/workspace/status-gates.d.ts.map +1 -1
  170. package/dist/workspace/status-gates.js +8 -7
  171. package/dist/workspace/status.d.ts.map +1 -1
  172. package/dist/workspace/status.js +4 -1
  173. package/dist/workspace/trust/seal.d.ts +2 -2
  174. package/dist/workspace/trust/seal.d.ts.map +1 -1
  175. package/dist/workspace/trust/seal.js +2 -2
  176. package/package.json +1 -1
  177. package/src/__fixtures__/oidc-emulator.ts +118 -0
  178. package/src/ci-handover.test.ts +209 -0
  179. package/src/ci-handover.ts +237 -0
  180. package/src/ci-identity.test.ts +201 -0
  181. package/src/ci-identity.ts +310 -0
  182. package/src/cli/handlers/approve.ts +18 -5
  183. package/src/cli/handlers/build.ts +15 -1
  184. package/src/cli/handlers/fan-out.ts +17 -4
  185. package/src/cli/handlers/gate-resume.ts +16 -2
  186. package/src/cli/handlers/lock.test.ts +98 -0
  187. package/src/cli/handlers/lock.ts +202 -0
  188. package/src/cli/handlers/operator.test.ts +3 -1
  189. package/src/cli/handlers/pr-env.ts +462 -0
  190. package/src/cli/handlers/pr-waves-blocked.test.ts +247 -0
  191. package/src/cli/handlers/pr.test.ts +184 -0
  192. package/src/cli/handlers/pr.ts +496 -59
  193. package/src/cli/handlers/promote.ts +9 -0
  194. package/src/cli/handlers/query.ts +78 -0
  195. package/src/cli/main.ts +41 -2
  196. package/src/cli/parse-args.ts +23 -2
  197. package/src/cli/registry.ts +12 -2
  198. package/src/components/fan-out-run.ts +39 -2
  199. package/src/components/fan-out-waves.test.ts +45 -1
  200. package/src/components/pr-environment.test.ts +249 -0
  201. package/src/components/pr-environment.ts +538 -0
  202. package/src/components/pr-hooks.test.ts +118 -0
  203. package/src/components/pr-hooks.ts +157 -0
  204. package/src/components/pr-pipeline.ts +130 -5
  205. package/src/components/pr-policy.test.ts +145 -0
  206. package/src/components/pr-policy.ts +211 -0
  207. package/src/components/pr-run.ts +5 -2
  208. package/src/gate-decision-bundle.test.ts +4 -3
  209. package/src/gate-decision.ts +39 -2
  210. package/src/gate-policy-bundle.test.ts +28 -0
  211. package/src/gate-policy.test.ts +162 -0
  212. package/src/gate-policy.ts +298 -0
  213. package/src/lifecycle/gate-checkpoint.ts +1 -1
  214. package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
  215. package/src/lifecycle/query.test.ts +91 -0
  216. package/src/lifecycle/query.ts +315 -0
  217. package/src/lifecycle/run-ledger.ts +18 -0
  218. package/src/lifecycle/target-lock.test.ts +195 -0
  219. package/src/lifecycle/target-lock.ts +347 -0
  220. package/src/op/activities/activity-contracts.ts +31 -0
  221. package/src/op/activities/index.ts +6 -0
  222. package/src/op/activities/review-agent.test.ts +240 -0
  223. package/src/op/activities/review-agent.ts +355 -0
  224. package/src/op/builders.ts +13 -0
  225. package/src/op/cron.ts +44 -0
  226. package/src/op/gate-alias.test.ts +2 -2
  227. package/src/op/gate-approval.test.ts +30 -9
  228. package/src/op/gate-approval.ts +23 -26
  229. package/src/op/gate-quorum.test.ts +139 -0
  230. package/src/op/gate-quorum.ts +248 -0
  231. package/src/op/gate-resume.ts +6 -3
  232. package/src/op/gate-review.ts +1 -1
  233. package/src/op/gate.ts +64 -23
  234. package/src/op/index.ts +13 -1
  235. package/src/op/local-executor.ts +9 -2
  236. package/src/op/op-waves-hook-targets.test.ts +126 -0
  237. package/src/op/op-waves-policy-hold.test.ts +66 -0
  238. package/src/op/op-waves-quorum.test.ts +101 -0
  239. package/src/op/op-waves-run.ts +233 -11
  240. package/src/op/op-waves.ts +83 -10
  241. package/src/op/schedule-overdue.test.ts +220 -0
  242. package/src/op/schedule-overdue.ts +161 -0
  243. package/src/override-gate-bundle.test.ts +1 -1
  244. package/src/override-gate.ts +2 -2
  245. package/src/pr-forge-bundle.test.ts +2 -2
  246. package/src/pr-forge.test.ts +33 -0
  247. package/src/pr-forge.ts +114 -0
  248. package/src/pr-loop-bundle.test.ts +2 -2
  249. package/src/pr-loop.test.ts +58 -0
  250. package/src/pr-loop.ts +146 -4
  251. package/src/pr-post.test.ts +141 -0
  252. package/src/pr-post.ts +159 -0
  253. package/src/wave-blocked-bundle.test.ts +25 -0
  254. package/src/wave-blocked.test.ts +146 -0
  255. package/src/wave-blocked.ts +271 -0
  256. package/src/wave-engine.ts +8 -0
  257. package/src/workspace/__fixtures__/two-member-workspace.ts +2 -2
  258. package/src/workspace/ci-branches.test.ts +62 -0
  259. package/src/workspace/ci-branches.ts +79 -0
  260. package/src/workspace/ci-cloud-identity.test.ts +53 -0
  261. package/src/workspace/ci-cloud-identity.ts +23 -0
  262. package/src/workspace/ci-locks.ts +16 -0
  263. package/src/workspace/ci-run-index.test.ts +2 -1
  264. package/src/workspace/ci-run.test.ts +107 -8
  265. package/src/workspace/ci-run.ts +72 -4
  266. package/src/workspace/declaration.schema.json +147 -0
  267. package/src/workspace/declaration.test.ts +76 -0
  268. package/src/workspace/declaration.ts +134 -2
  269. package/src/workspace/identity-gate-rule.ts +1 -1
  270. package/src/workspace/identity.ts +1 -1
  271. package/src/workspace/lineage-upgrade.ts +11 -8
  272. package/src/workspace/member-pipeline.test.ts +29 -0
  273. package/src/workspace/member-pipeline.ts +30 -0
  274. package/src/workspace/pr-report.schema.json +73 -2
  275. package/src/workspace/security.ts +4 -0
  276. package/src/workspace/status-gates.ts +9 -6
  277. package/src/workspace/status.ts +4 -1
  278. package/src/workspace/trust/gate-seal.test.ts +1 -1
  279. package/src/workspace/trust/seal.ts +2 -2
  280. package/dist/workspace/trust/gate-seal.d.ts.map +0 -1
  281. /package/dist/{workspace/trust → lifecycle}/gate-seal.js +0 -0
package/dist/pr-loop.js CHANGED
@@ -63,6 +63,12 @@ export const PR_REPORT_WAVES_FLOOR = "0.124.32";
63
63
  export const PR_REPORT_PLANNED_OUTPUTS_FLOOR = "0.124.32";
64
64
  /** The chant that writes the `record` stage and the `recorded` status (#4168 step 1). */
65
65
  export const PR_REPORT_RECORD_FLOOR = "0.124.33";
66
+ /** The first chant whose report can carry `hooks` (#4288). */
67
+ export const PR_REPORT_HOOKS_FLOOR = "0.124.41";
68
+ /** The first chant whose plan report can carry `overdue` (#4289). */
69
+ export const PR_REPORT_OVERDUE_FLOOR = "0.124.41";
70
+ /** The first chant whose report can carry `policy` and the `policy-denied` and `policy-error` refusals (#4294). */
71
+ export const PR_REPORT_POLICY_FLOOR = "0.124.41";
66
72
  /** The gate a pull request's apply waits on, unless the pipeline names another. */
67
73
  export const PR_APPLY_GATE = "pr-apply";
68
74
  /**
@@ -143,11 +149,15 @@ function headline(report) {
143
149
  case "recorded":
144
150
  return `Recorded the plan of ${members} on merge. Nothing applied: a release that carries this commit deploys it.`;
145
151
  case "plan-failed":
152
+ if (report.hooks?.some((h) => h.outcome === "failed"))
153
+ return "A hook failed, so nothing can apply.";
146
154
  return `Planning failed for ${report.members.filter((m) => m.status === "plan-failed").length} of ${members}, so nothing can apply.`;
147
155
  case "applied":
148
156
  return `Applied ${members}.`;
149
- case "failed":
150
- return `The apply failed: ${report.members.filter((m) => m.status === "failed").length} failed, ${report.members.filter((m) => m.status === "applied").length} applied.`;
157
+ case "failed": {
158
+ const blocked = report.members.filter((m) => m.status === "blocked").length;
159
+ return `The apply failed: ${report.members.filter((m) => m.status === "failed").length} failed, ${blocked > 0 ? `${blocked} blocked, ` : ""}${report.members.filter((m) => m.status === "applied").length} applied.`;
160
+ }
151
161
  case "wave-applied": {
152
162
  const applied = (report.waves ?? []).filter((w) => w.status === "applied").map((w) => w.wave);
153
163
  const total = report.selection.waves.length;
@@ -161,6 +171,49 @@ function headline(report) {
161
171
  return `Applied ${report.members.filter((m) => m.status === "applied").length} of ${members}; ${report.members.filter((m) => m.status === "waiting").length} wait on an approval of the plan made after their upstream applied.`;
162
172
  }
163
173
  }
174
+ /**
175
+ * The blocked set (#4291): each component a failure held, grouped by the
176
+ * component that failed, and that the rest went on. Undefined when nothing
177
+ * is blocked.
178
+ */
179
+ function blockedLine(members) {
180
+ const byRoot = new Map();
181
+ for (const m of members) {
182
+ if (m.status !== "blocked")
183
+ continue;
184
+ const by = m.blockedBy ?? "a dependency";
185
+ byRoot.set(by, (byRoot.get(by) ?? new Set()).add(m.component));
186
+ }
187
+ if (byRoot.size === 0)
188
+ return undefined;
189
+ const groups = [...byRoot]
190
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
191
+ .map(([by, names]) => `${[...names].sort().map(code).join(", ")} (blocked by ${by === "a dependency" ? by : code(by)})`);
192
+ return `Blocked, so not applied: ${groups.join("; ")}. A failure blocks only the members that depend on it, so the rest went on.`;
193
+ }
194
+ /** The policy's lines in the note (#4294): what denied or held the plan, and how a denial is overridden. */
195
+ function policyLines(report) {
196
+ const p = report.policy;
197
+ if (!p)
198
+ return [];
199
+ const at = `read at ${code(short(p.base))}`;
200
+ const each = (list) => list.map((f) => `- ${code(f.rule)}: ${f.message}`);
201
+ const out = [""];
202
+ if (p.result === "error")
203
+ out.push(`Policy (${p.engine}, ${at}) could not be checked, so nothing applies: ${p.error ?? "no answer"}.`);
204
+ else if (p.result === "passed")
205
+ out.push(`Policy (${p.engine}, ${at}) passed.`);
206
+ if (p.denials.length > 0) {
207
+ out.push(p.override ? `Policy (${p.engine}, ${at}) denied this plan; ${code(p.override.by)} overrode it: ${p.override.reason}` : `Policy (${p.engine}, ${at}) denied this plan:`, ...each(p.denials));
208
+ if (!p.override && p.overrideCommand)
209
+ out.push("", `A person the overriders file at base lists may override it once pr-apply has recorded the denial: ${code(p.overrideCommand)}`);
210
+ }
211
+ if (p.holds.length > 0)
212
+ out.push(`Policy (${p.engine}, ${at}) holds this plan for a person's approval of its digest:`, ...each(p.holds));
213
+ if (p.warnings.length > 0)
214
+ out.push(`Policy warnings: ${p.warnings.join("; ")}`);
215
+ return out;
216
+ }
164
217
  /** Who a wave applied or waits under, as a table cell. */
165
218
  function waveApproval(w) {
166
219
  const whose = w.approval === "pull-request" ? "the pull request's" : "its own";
@@ -219,6 +272,8 @@ export function renderPrNote(report, options = {}) {
219
272
  ];
220
273
  if (report.message)
221
274
  out.push("", `> ${report.message.split("\n").join("\n> ")}`);
275
+ for (const o of report.overdue ?? [])
276
+ out.push("", `Warning: ${o.message}`);
222
277
  if (report.resumed && report.resumed.applied.length > 0) {
223
278
  out.push("", `Resumed under the same approval: ${report.resumed.applied.map(code).join(", ")} applied in an earlier attempt and did not run again.`);
224
279
  }
@@ -227,6 +282,7 @@ export function renderPrNote(report, options = {}) {
227
282
  for (const m of report.members) {
228
283
  const holes = memberHoleReasons(report, m.member);
229
284
  const status = STATUS_WORDS[m.status] +
285
+ (m.status === "blocked" && m.blockedBy ? ` by ${code(m.blockedBy)}` : "") +
230
286
  (m.inputsMoved?.length ? `, inputs from ${m.inputsMoved.join(", ")} moved` : "") +
231
287
  (holes.length > 0 ? `; ${holes.length > 2 ? `${holes[0]}; and ${holes.length - 1} more, listed under Holes` : holes.join("; ")}` : "");
232
288
  // A member whose plan has holes and no entries says nothing about what
@@ -239,7 +295,18 @@ export function renderPrNote(report, options = {}) {
239
295
  out.push("", "A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.");
240
296
  }
241
297
  }
298
+ const blocked = blockedLine(report.members);
299
+ if (blocked)
300
+ out.push("", blocked);
242
301
  out.push(...waveLines(report, options.approver ?? "<you>"));
302
+ // #4288: the hooks that did not pass, by name; a passed hook is a count.
303
+ const hooks = report.hooks ?? [];
304
+ if (hooks.length > 0) {
305
+ const passed = hooks.filter((h) => h.outcome === "passed").length;
306
+ const other = hooks.filter((h) => h.outcome !== "passed").map((h) => `${code(h.name)} ${h.moment}${h.target ? ` for ${code(h.target)}` : ""} ${h.outcome === "held" ? "held" : "failed"} (exit ${h.exit})`);
307
+ out.push("", `Hooks: ${passed} passed${other.length > 0 ? `; ${other.join("; ")}` : ""}.`);
308
+ }
309
+ out.push(...policyLines(report));
243
310
  if (report.selection.unclaimed.length > 0) {
244
311
  out.push("", `Changed, and deployed by no component: ${report.selection.unclaimed.map(code).join(", ")}.`);
245
312
  }
@@ -334,7 +401,11 @@ export function prStatusDescription(report) {
334
401
  text = "refused: an earlier wave has not applied";
335
402
  break;
336
403
  }
337
- text = `refused: ${report.refusal === "plan-changed" ? "the plan changed after review" : report.refusal === "not-a-reviewer" ? "the approver has not approved the pull request" : report.refusal === "author-approved" ? "the approver wrote the change" : report.refusal === "no-pull-request" ? "no pull request merged this commit" : "the plan is not approved"}`;
404
+ if (report.refusal === "policy-denied" || report.refusal === "policy-error") {
405
+ text = report.refusal === "policy-denied" ? `refused: the policy denied the plan (${(report.policy?.rules ?? []).join(", ")})` : "refused: the policy could not be checked";
406
+ break;
407
+ }
408
+ text = `refused: ${report.refusal === "plan-changed" ? "the plan changed after review" : report.refusal === "not-a-reviewer" ? "the approver has not approved the pull request" : report.refusal === "author-approved" ? "the approver wrote the change" : report.refusal === "no-pull-request" ? "no pull request merged this commit" : report.refusal === "locked" ? "another pull request holds a target lock" : "the plan is not approved"}`;
338
409
  break;
339
410
  }
340
411
  }
@@ -345,6 +416,9 @@ export function prStatusState(report) {
345
416
  // #4168 decision 1: a merge whose plan differs from the approved one is recorded, and its status fails.
346
417
  if (report.status === "recorded")
347
418
  return report.approval.status === "changed" ? "failure" : "success";
419
+ // #4294: a plan the policy denied, with no override, or one it could not check, fails at the plan stage as it would at the apply.
420
+ if (report.status === "planned" && report.policy && (report.policy.result === "error" || (report.policy.result === "denied" && !report.policy.override)))
421
+ return "failure";
348
422
  if (report.status === "planned" || report.status === "applied" || report.status === "wave-applied" || report.status === "nothing")
349
423
  return "success";
350
424
  if (report.status === "refused" && (report.refusal === "not-approved" || report.refusal === "wave-not-approved"))
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Post a pull request's plan note and its `chant/plan` status from the plan
3
+ * report (#4295), in a job or a process that runs none of the change's code.
4
+ *
5
+ * The plan job runs the pull request's code and holds no forge token
6
+ * (./ci-handover.ts), so `pr-plan --no-status` only writes its report. Then
7
+ * `chant components pr-post` in the `plan-note` job, or `chant ci run plan`
8
+ * once its steps settle, reads the report and posts.
9
+ *
10
+ * The report is the change's: its code could write anything into it. So it
11
+ * is read as data. The pull request, the head, the environment and the
12
+ * member come from the caller, never the report, and a report that names
13
+ * others is refused. The note is rendered again here from the report, and
14
+ * every hidden HTML comment after its first line is defused, so a forged
15
+ * marker cannot claim another environment's note. A plan job that failed
16
+ * fails the status whatever its report says.
17
+ */
18
+ import { type PrOverdue, type PrReport } from "./pr-loop.js";
19
+ import { type PrForge } from "./pr-forge.js";
20
+ /** The plan report's file under the report directory. */
21
+ export declare const PR_PLAN_REPORT_FILE = "pr-plan.json";
22
+ /** What {@link postPlanReport} posts for. */
23
+ export interface PlanPostInput {
24
+ forge: PrForge;
25
+ /** The report directory the plan job wrote, `.chant/pr`. */
26
+ dir: string;
27
+ /** The pull request, from the forge's event. */
28
+ pr: number;
29
+ /** The commit the plan job checked out, from the forge's event. */
30
+ head: string;
31
+ env: string;
32
+ member?: string;
33
+ /** The plan job's own result (`needs.plan.result`). `failure` fails the status. */
34
+ result?: string;
35
+ /**
36
+ * The scheduled Ops that stopped running (#4289), asked here since the plan
37
+ * job held no token to ask with. It replaces any `overdue` in the report.
38
+ */
39
+ overdue?: () => Promise<PrOverdue[]>;
40
+ }
41
+ /** What {@link postPlanReport} did. */
42
+ export interface PlanPostResult {
43
+ state: "success" | "failure" | "pending";
44
+ description: string;
45
+ /** Whether a note was posted. A refused or missing report posts none. */
46
+ noted: boolean;
47
+ /** Why the report was not used, when it was not. */
48
+ refused?: string;
49
+ warnings: string[];
50
+ }
51
+ /**
52
+ * Read the plan report under `dir` and check it is the one the caller ran:
53
+ * a plan report for the same pull request, head, environment and member.
54
+ * Returns the report, or why it is not used.
55
+ */
56
+ export declare function readPlanReport(input: Pick<PlanPostInput, "dir" | "pr" | "head" | "env" | "member">): {
57
+ report: PrReport;
58
+ } | {
59
+ refused: string;
60
+ };
61
+ /**
62
+ * The note for `report`, rendered here, with every `<!--` after the marker
63
+ * line written as `&lt;!--`, so the note carries no hidden marker but its own.
64
+ */
65
+ export declare function planNoteFor(report: PrReport, forge: Pick<PrForge, "kind" | "host">, marker: string): string;
66
+ /**
67
+ * Post the status and the note for the plan report under `input.dir`. A
68
+ * forge that refuses a call is a warning. A report that is missing or not
69
+ * the caller's sets a failed status and posts no note.
70
+ */
71
+ export declare function postPlanReport(input: PlanPostInput): Promise<PlanPostResult>;
72
+ //# sourceMappingURL=pr-post.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-post.d.ts","sourceRoot":"","sources":["../src/pr-post.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,EAAmF,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3I,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5D,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,6CAA6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACtC;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAoB/I;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ3G;AAID;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAkDlF"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Post a pull request's plan note and its `chant/plan` status from the plan
3
+ * report (#4295), in a job or a process that runs none of the change's code.
4
+ *
5
+ * The plan job runs the pull request's code and holds no forge token
6
+ * (./ci-handover.ts), so `pr-plan --no-status` only writes its report. Then
7
+ * `chant components pr-post` in the `plan-note` job, or `chant ci run plan`
8
+ * once its steps settle, reads the report and posts.
9
+ *
10
+ * The report is the change's: its code could write anything into it. So it
11
+ * is read as data. The pull request, the head, the environment and the
12
+ * member come from the caller, never the report, and a report that names
13
+ * others is refused. The note is rendered again here from the report, and
14
+ * every hidden HTML comment after its first line is defused, so a forged
15
+ * marker cannot claim another environment's note. A plan job that failed
16
+ * fails the status whatever its report says.
17
+ */
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { prNoteMarker, prStatusContext, prStatusDescription, prStatusState, renderPrNote } from "./pr-loop.js";
21
+ import { forgePrincipalOf } from "./pr-forge.js";
22
+ import { GITHUB_COMMENT_LIMIT, GITLAB_NOTE_LIMIT } from "./plan-summary.js";
23
+ /** The plan report's file under the report directory. */
24
+ export const PR_PLAN_REPORT_FILE = "pr-plan.json";
25
+ const SHA = /^[0-9a-f]{40}$/;
26
+ /**
27
+ * Read the plan report under `dir` and check it is the one the caller ran:
28
+ * a plan report for the same pull request, head, environment and member.
29
+ * Returns the report, or why it is not used.
30
+ */
31
+ export function readPlanReport(input) {
32
+ const file = join(input.dir, PR_PLAN_REPORT_FILE);
33
+ if (!existsSync(file))
34
+ return { refused: "the plan job wrote no report" };
35
+ let doc;
36
+ try {
37
+ doc = JSON.parse(readFileSync(file, "utf-8"));
38
+ }
39
+ catch (err) {
40
+ return { refused: `the plan report does not read: ${err instanceof Error ? err.message : String(err)}` };
41
+ }
42
+ if (!doc || typeof doc !== "object" || Array.isArray(doc))
43
+ return { refused: "the plan report is not an object" };
44
+ const r = doc;
45
+ if (r.stage !== "plan")
46
+ return { refused: `the report is not a plan report (stage ${JSON.stringify(r.stage)})` };
47
+ if (r.pr !== input.pr)
48
+ return { refused: `the report is for pull request ${JSON.stringify(r.pr)}, not ${input.pr}` };
49
+ if (typeof r.head !== "string" || !SHA.test(r.head) || r.head !== input.head)
50
+ return { refused: `the report is for head ${JSON.stringify(r.head)}, not ${input.head}` };
51
+ if (r.env !== input.env)
52
+ return { refused: `the report is for environment ${JSON.stringify(r.env)}, not ${input.env}` };
53
+ if ((r.member ?? undefined) !== input.member)
54
+ return { refused: `the report is for member ${JSON.stringify(r.member ?? null)}, not ${JSON.stringify(input.member ?? null)}` };
55
+ if (!Array.isArray(r.members) || !r.changeSet || typeof r.changeSet !== "object" || !r.selection || typeof r.selection !== "object" || !r.approval || typeof r.approval !== "object") {
56
+ return { refused: "the plan report is missing its members, change set, selection or approval" };
57
+ }
58
+ return { report: doc };
59
+ }
60
+ /**
61
+ * The note for `report`, rendered here, with every `<!--` after the marker
62
+ * line written as `&lt;!--`, so the note carries no hidden marker but its own.
63
+ */
64
+ export function planNoteFor(report, forge, marker) {
65
+ const note = renderPrNote(report, {
66
+ limit: forge.kind === "gitlab" ? GITLAB_NOTE_LIMIT : GITHUB_COMMENT_LIMIT,
67
+ approver: forgePrincipalOf(forge.kind, forge.host, "<your-login>"),
68
+ });
69
+ const at = note.indexOf("\n");
70
+ const rest = (at < 0 ? "" : note.slice(at + 1)).split("<!--").join("&lt;!--");
71
+ return `${marker}\n${rest}`;
72
+ }
73
+ const cut = (text, max = 140) => (text.length > max ? `${text.slice(0, max - 3)}...` : text);
74
+ /**
75
+ * Post the status and the note for the plan report under `input.dir`. A
76
+ * forge that refuses a call is a warning. A report that is missing or not
77
+ * the caller's sets a failed status and posts no note.
78
+ */
79
+ export async function postPlanReport(input) {
80
+ const warnings = [];
81
+ const context = prStatusContext("plan", input.member);
82
+ const read = readPlanReport(input);
83
+ const failedJob = input.result === "failure" || input.result === "cancelled";
84
+ let state;
85
+ let description;
86
+ let note;
87
+ let refused;
88
+ if ("refused" in read) {
89
+ refused = read.refused;
90
+ state = "failure";
91
+ description = cut(read.refused);
92
+ }
93
+ else {
94
+ let report = read.report;
95
+ if (input.overdue) {
96
+ const { overdue: _forged, ...rest } = report;
97
+ const found = await input.overdue().catch(() => []);
98
+ report = (found.length > 0 ? { ...rest, overdue: found } : rest);
99
+ }
100
+ try {
101
+ state = prStatusState(report);
102
+ description = prStatusDescription(report);
103
+ // A member the change does not reach has nothing to say on the pull request (as pr-plan's own publish).
104
+ if (!(input.member && report.status === "nothing" && !report.overdue?.length))
105
+ note = planNoteFor(report, input.forge, prNoteMarker(input.env, input.member));
106
+ }
107
+ catch (err) {
108
+ refused = `the plan report does not render: ${err instanceof Error ? err.message : String(err)}`;
109
+ state = "failure";
110
+ description = cut(refused);
111
+ note = undefined;
112
+ }
113
+ if (failedJob && state !== "failure") {
114
+ state = "failure";
115
+ description = cut(`the plan job failed; ${description}`);
116
+ }
117
+ }
118
+ try {
119
+ await input.forge.setStatus(input.head, { context, state, description: cut(description), ...(input.forge.runUrl ? { url: input.forge.runUrl } : {}) });
120
+ }
121
+ catch (err) {
122
+ warnings.push(`could not set the ${context} status: ${err instanceof Error ? err.message : String(err)}`);
123
+ }
124
+ if (note !== undefined) {
125
+ try {
126
+ await input.forge.upsertNote(input.pr, prNoteMarker(input.env, input.member), note);
127
+ }
128
+ catch (err) {
129
+ warnings.push(`could not post the note on #${input.pr}: ${err instanceof Error ? err.message : String(err)}`);
130
+ note = undefined;
131
+ }
132
+ }
133
+ return { state, description: cut(description), noted: note !== undefined, ...(refused ? { refused } : {}), warnings };
134
+ }
@@ -0,0 +1,79 @@
1
+ export declare const WAVE_OUTCOME_VERSION = 1;
2
+ export type WaveMemberStatus = "applied" | "unchanged" | "failed" | "waiting" | "refused" | "decided" | "not-applied" | "blocked";
3
+ export interface WaveMemberOutcome {
4
+ status: WaveMemberStatus;
5
+ /** A blocked member's: the member whose outcome held it, past any member blocked in between. */
6
+ by?: string;
7
+ }
8
+ export interface WaveOutcomeFile {
9
+ version: typeof WAVE_OUTCOME_VERSION;
10
+ wave: number;
11
+ /** When the job wrote it: the newest outcome of a member wins. */
12
+ at: string;
13
+ members: Record<string, WaveMemberOutcome>;
14
+ /** The wave holds every later member, each blocked by `wave-<k>`. */
15
+ holds_all?: true;
16
+ }
17
+ /** Whether `outcome` lets a dependent through: it applied, or had nothing to apply. */
18
+ export declare function passesThrough(outcome: WaveMemberOutcome | undefined): boolean;
19
+ /**
20
+ * The members of `members` an upstream that did not apply holds, each with
21
+ * the member whose outcome holds it. `upstreams` is each member's upstreams;
22
+ * only those in `earlier` count (a member of this wave or a later one is no
23
+ * upstream it waits for here). An upstream with no outcome did not apply as
24
+ * far as this run knows, so it holds its dependents and names itself. A
25
+ * blocked upstream passes on the member that blocked it. With `holdsAll`,
26
+ * every member is blocked by `wave-<holdsAll>`.
27
+ */
28
+ export declare function blockedMembers(members: readonly string[], upstreams: ReadonlyMap<string, Iterable<string>>, earlier: ReadonlySet<string>, outcomes: ReadonlyMap<string, WaveMemberOutcome>, holdsAll?: number): Map<string, string>;
29
+ /**
30
+ * Walk waves 1 to `before - 1` and give each member that has no outcome the
31
+ * one it ends with: `blocked` behind an upstream that did not apply. A member
32
+ * with no outcome that nothing blocks has not run yet: it is in `unsettled`,
33
+ * and wave `before` cannot run until it has. `outcomes` is what the records
34
+ * say: `applied` for what applied, and `failed`, `waiting` or `not-applied`
35
+ * for what did not. Returns those outcomes with the blocked members added.
36
+ */
37
+ export declare function settleEarlierWaves(waves: readonly (readonly string[])[], before: number, upstreams: ReadonlyMap<string, Iterable<string>>, outcomes: ReadonlyMap<string, WaveMemberOutcome>): {
38
+ outcomes: Map<string, WaveMemberOutcome>;
39
+ unsettled: string[];
40
+ };
41
+ /**
42
+ * Whether every member that did not apply waits for an approval, or is
43
+ * blocked behind one that does: the run waits rather than failed, so the
44
+ * job ends as waiting (exit 3), and the approval's re-run applies them.
45
+ */
46
+ export declare function onlyWaiting(outcomes: ReadonlyMap<string, WaveMemberOutcome>): boolean;
47
+ /** The blocked members grouped by what blocked them, as a log and a note list them: `app-01, app-02 (blocked by cluster-a)`. */
48
+ export declare function blockedWords(blocked: ReadonlyMap<string, string>): string;
49
+ /**
50
+ * How the run came out, from every member's newest outcome: "8 applied, 1
51
+ * failed, 6 blocked", with waiting, refused and the rest named when there
52
+ * are any, and undefined when every member applied or had nothing to apply.
53
+ */
54
+ export declare function runSummary(outcomes: Iterable<WaveMemberOutcome>): string | undefined;
55
+ /** The outcome file of a wave's job, or of one group or share of it. */
56
+ export declare function waveOutcomeFileName(wave: number, part?: {
57
+ group?: string;
58
+ share?: number;
59
+ }): string;
60
+ /** Write a wave job's outcome file in `dir`, members sorted by name. Returns its path. */
61
+ export declare function writeWaveOutcomeFile(dir: string, wave: number, part: {
62
+ group?: string;
63
+ share?: number;
64
+ }, members: Record<string, WaveMemberOutcome>, at?: string, holdsAll?: boolean): string;
65
+ /**
66
+ * The outcome of each member of the waves before `wave`, as their jobs wrote
67
+ * them under `dirs`, and the earliest such wave that holds every later
68
+ * member, if any. For a member more than one job wrote (a re-run, or a split
69
+ * wave's deciding job and the share that applied it), the newest. A file
70
+ * that does not parse, or is of another version, is skipped.
71
+ */
72
+ export declare function earlierWaveOutcomes(dirs: readonly string[], wave: number): {
73
+ members: Map<string, WaveMemberOutcome & {
74
+ wave: number;
75
+ at: string;
76
+ }>;
77
+ holdsAll?: number;
78
+ };
79
+ //# sourceMappingURL=wave-blocked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wave-blocked.d.ts","sourceRoot":"","sources":["../src/wave-blocked.ts"],"names":[],"mappings":"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"}