@intentius/chant 0.116.0 → 0.118.0

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 (47) hide show
  1. package/dist/build.d.ts +1 -1
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/handlers/gate-resume.d.ts +44 -0
  5. package/dist/cli/handlers/gate-resume.d.ts.map +1 -0
  6. package/dist/cli/handlers/operator.d.ts +1 -0
  7. package/dist/cli/handlers/operator.d.ts.map +1 -1
  8. package/dist/cli/handlers/run-wave.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/registry.d.ts +8 -0
  11. package/dist/cli/registry.d.ts.map +1 -1
  12. package/dist/discovery/files.d.ts +8 -0
  13. package/dist/discovery/files.d.ts.map +1 -1
  14. package/dist/lexicon.d.ts +10 -1
  15. package/dist/lexicon.d.ts.map +1 -1
  16. package/dist/lifecycle/gate-ledger.d.ts +18 -0
  17. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  18. package/dist/op/gate-resume.d.ts +104 -0
  19. package/dist/op/gate-resume.d.ts.map +1 -0
  20. package/dist/op/gate-review.d.ts +113 -0
  21. package/dist/op/gate-review.d.ts.map +1 -0
  22. package/dist/op/gate.d.ts.map +1 -1
  23. package/dist/op/op-waves-run.d.ts +47 -1
  24. package/dist/op/op-waves-run.d.ts.map +1 -1
  25. package/dist/op/op-waves.d.ts +39 -0
  26. package/dist/op/op-waves.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/build.ts +13 -2
  29. package/src/cli/commands/build.ts +2 -1
  30. package/src/cli/handlers/gate-resume.test.ts +51 -0
  31. package/src/cli/handlers/gate-resume.ts +166 -0
  32. package/src/cli/handlers/operator.ts +8 -2
  33. package/src/cli/handlers/run-wave.ts +13 -1
  34. package/src/cli/main.ts +21 -3
  35. package/src/cli/plugins.ts +1 -1
  36. package/src/cli/registry.ts +8 -0
  37. package/src/discovery/files.ts +18 -0
  38. package/src/lexicon.ts +10 -1
  39. package/src/lifecycle/gate-ledger.ts +14 -0
  40. package/src/op/gate-resume.e2e.test.ts +135 -0
  41. package/src/op/gate-resume.test.ts +177 -0
  42. package/src/op/gate-resume.ts +267 -0
  43. package/src/op/gate-review.test.ts +225 -0
  44. package/src/op/gate-review.ts +339 -0
  45. package/src/op/gate.ts +4 -0
  46. package/src/op/op-waves-run.ts +191 -13
  47. package/src/op/op-waves.ts +51 -0
@@ -27,6 +27,12 @@
27
27
  * the same digest a component fan-out's wave binds). Whether it waits at all
28
28
  * is the wave's gate policy, read from the base commit's copy of the spec
29
29
  * file, so a change cannot loosen the rule that gates its own merge.
30
+ *
31
+ * So is which approvals count (#3684), the wave's `approval`: `ledger` (any
32
+ * `chant approve` of the digest), `sealed` (only one whose seal verifies
33
+ * against the signers file at base) or `pr-review` (also the merged pull
34
+ * request's review of its head, by a writer other than its author, when the
35
+ * head planned the same digest). See `./gate-review.ts`.
30
36
  */
31
37
 
32
38
  import type { OpEnvironment, OpSetupStep } from "../lexicon";
@@ -42,6 +48,17 @@ export type OpWaveGatePolicy =
42
48
 
43
49
  export const OP_WAVE_GATE_POLICIES: readonly OpWaveGatePolicy[] = ["always", "on-destructive", "never"];
44
50
 
51
+ /** Which approvals of a gate's digest count (#3684). See `./gate-review.ts`. */
52
+ export type GateApprovalSource =
53
+ /** Any resolution on `chant/lifecycle` that names the digest. */
54
+ | "ledger"
55
+ /** As `ledger`, and the merged pull request's review of its head, by a writer other than its author. */
56
+ | "pr-review"
57
+ /** Only a resolution sealed by a key the signers file at base lists for its approver. */
58
+ | "sealed";
59
+
60
+ export const GATE_APPROVAL_SOURCES: readonly GateApprovalSource[] = ["ledger", "pr-review", "sealed"];
61
+
45
62
  /** One Op run in a wave: an Op applied to one target (an environment, a database, a tenant). */
46
63
  export interface OpWaveRun {
47
64
  /** What the run applies to. Unique within its wave; it names the run in the set digest. */
@@ -60,6 +77,11 @@ export interface OpWave {
60
77
  * spec file at the base commit, never from the change being applied.
61
78
  */
62
79
  gate?: OpWaveGatePolicy;
80
+ /**
81
+ * Which approvals of the wave's digest count (#3684). Default `"ledger"`.
82
+ * Like `gate`, the runner reads it from the spec file at the base commit.
83
+ */
84
+ approval?: GateApprovalSource;
63
85
  /**
64
86
  * Split the wave into a deciding job and this many share jobs, each
65
87
  * applying a slice of the runs. Default 1: one job plans, decides and
@@ -103,6 +125,29 @@ export interface OpWavesSpec {
103
125
  /** Apply one run whose plan is at `{plan}`. Default `chant run {op} --env {target}`. */
104
126
  apply?: string[];
105
127
  waves: OpWave[];
128
+ /**
129
+ * A scheduled job that starts again a wave job whose approval has arrived
130
+ * (#3683): it runs `chant run resume --op <name>`, which re-runs the
131
+ * GitHub run, retries the GitLab job or dispatches the Forgejo workflow.
132
+ * It approves nothing. Absent, no such job is rendered and an approval is
133
+ * followed by `chant approve --resume` or a re-run by hand.
134
+ */
135
+ resume?: OpWavesResume;
136
+ }
137
+
138
+ /** The scheduled resume job of an Op waves pipeline (#3683). */
139
+ export interface OpWavesResume {
140
+ /**
141
+ * Five-field cron, such as `"*\/10 * * * *"`. GitHub and Forgejo put it
142
+ * in the workflow; GitLab runs the job in a pipeline schedule you create,
143
+ * which sets the cron.
144
+ */
145
+ schedule: string;
146
+ }
147
+
148
+ /** The command a resume job runs. */
149
+ export function opWavesResumeCommand(spec: Pick<OpWavesSpec, "name">): string[] {
150
+ return ["chant", "run", "resume", "--op", spec.name];
106
151
  }
107
152
 
108
153
  /** The default apply command. */
@@ -146,6 +191,9 @@ export function assertOpWavesSpec(spec: OpWavesSpec): void {
146
191
  throw new Error(`${where}: the \`plan\` command never names {plan}, the file it writes the plan digest to.`);
147
192
  }
148
193
  if (spec.waves.length === 0) throw new Error(`${where} has no waves.`);
194
+ if (spec.resume !== undefined && (typeof spec.resume.schedule !== "string" || spec.resume.schedule.trim().split(/\s+/).length !== 5)) {
195
+ throw new Error(`${where}: resume.schedule must be a five-field cron expression, such as "*/10 * * * *".`);
196
+ }
149
197
  const names = new Set<string>();
150
198
  for (const wave of spec.waves) {
151
199
  if (!NAME.test(wave.name)) {
@@ -156,6 +204,9 @@ export function assertOpWavesSpec(spec: OpWavesSpec): void {
156
204
  if (wave.gate !== undefined && !OP_WAVE_GATE_POLICIES.includes(wave.gate)) {
157
205
  throw new Error(`${where}: wave "${wave.name}" has gate "${wave.gate}"; use ${OP_WAVE_GATE_POLICIES.join(", ")}.`);
158
206
  }
207
+ if (wave.approval !== undefined && !GATE_APPROVAL_SOURCES.includes(wave.approval)) {
208
+ throw new Error(`${where}: wave "${wave.name}" has approval "${wave.approval}"; use ${GATE_APPROVAL_SOURCES.join(", ")}.`);
209
+ }
159
210
  if (wave.runs.length === 0) throw new Error(`${where}: wave "${wave.name}" has no runs.`);
160
211
  const targets = new Set<string>();
161
212
  for (const run of wave.runs) {