@intentius/chant-lexicon-github 0.124.46 → 0.124.49

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 (69) hide show
  1. package/dist/action-pins.d.ts +17 -27
  2. package/dist/action-pins.d.ts.map +1 -1
  3. package/dist/action-pins.js +39 -27
  4. package/dist/components/ci-jobs.d.ts +5 -2
  5. package/dist/components/ci-jobs.d.ts.map +1 -1
  6. package/dist/components/ci-jobs.js +8 -5
  7. package/dist/components/generate-op-pipeline.d.ts +7 -0
  8. package/dist/components/generate-op-pipeline.d.ts.map +1 -1
  9. package/dist/components/generate-op-pipeline.js +26 -3
  10. package/dist/components/generate-op-waves-pipeline.d.ts +13 -3
  11. package/dist/components/generate-op-waves-pipeline.d.ts.map +1 -1
  12. package/dist/components/generate-op-waves-pipeline.js +24 -5
  13. package/dist/components/generate-pipeline.d.ts.map +1 -1
  14. package/dist/components/generate-pipeline.js +55 -10
  15. package/dist/components/review-jobs.d.ts +55 -0
  16. package/dist/components/review-jobs.d.ts.map +1 -0
  17. package/dist/components/review-jobs.js +130 -0
  18. package/dist/composites/cache.d.ts.map +1 -1
  19. package/dist/composites/cache.js +2 -1
  20. package/dist/composites/checkout.d.ts +3 -3
  21. package/dist/composites/checkout.js +1 -1
  22. package/dist/composites/docker-build.js +4 -4
  23. package/dist/composites/download-artifact.d.ts.map +1 -1
  24. package/dist/composites/download-artifact.js +2 -1
  25. package/dist/composites/go-ci.js +4 -4
  26. package/dist/composites/node-pipeline.js +5 -5
  27. package/dist/composites/python-ci.js +2 -2
  28. package/dist/composites/setup-go.d.ts.map +1 -1
  29. package/dist/composites/setup-go.js +2 -1
  30. package/dist/composites/setup-node.d.ts +3 -3
  31. package/dist/composites/setup-node.js +1 -1
  32. package/dist/composites/setup-python.d.ts.map +1 -1
  33. package/dist/composites/setup-python.js +2 -1
  34. package/dist/composites/upload-artifact.d.ts.map +1 -1
  35. package/dist/composites/upload-artifact.js +2 -1
  36. package/dist/integrity.json +2 -2
  37. package/dist/manifest.json +1 -1
  38. package/package.json +2 -2
  39. package/src/action-pins.test.ts +29 -6
  40. package/src/action-pins.ts +48 -34
  41. package/src/components/__fixtures__/pr-loop-build.github.golden.yml +8 -9
  42. package/src/components/__fixtures__/pr-loop-record.github.golden.yml +4 -5
  43. package/src/components/__fixtures__/pr-loop-waves.github.golden.yml +16 -17
  44. package/src/components/__fixtures__/pr-loop.github.golden.yml +8 -9
  45. package/src/components/__golden__/op-waves-jobs.yml +71 -9
  46. package/src/components/ci-jobs.ts +8 -5
  47. package/src/components/generate-op-pipeline.review.test.ts +152 -0
  48. package/src/components/generate-op-pipeline.test.ts +5 -4
  49. package/src/components/generate-op-pipeline.ts +33 -3
  50. package/src/components/generate-op-waves-pipeline.test.ts +2 -1
  51. package/src/components/generate-op-waves-pipeline.ts +25 -5
  52. package/src/components/generate-pipeline.test.ts +10 -9
  53. package/src/components/generate-pipeline.ts +57 -9
  54. package/src/components/generate-pr-pipeline.locks.test.ts +68 -0
  55. package/src/components/generate-pr-pipeline.test.ts +6 -4
  56. package/src/components/generate-pr-waves-pipeline.test.ts +3 -2
  57. package/src/components/review-jobs.ts +165 -0
  58. package/src/composites/cache.ts +2 -1
  59. package/src/composites/checkout.ts +4 -4
  60. package/src/composites/composites.test.ts +23 -22
  61. package/src/composites/docker-build.ts +4 -4
  62. package/src/composites/download-artifact.ts +2 -1
  63. package/src/composites/go-ci.ts +4 -4
  64. package/src/composites/node-pipeline.ts +5 -5
  65. package/src/composites/python-ci.ts +2 -2
  66. package/src/composites/setup-go.ts +2 -1
  67. package/src/composites/setup-node.ts +4 -4
  68. package/src/composites/setup-python.ts +2 -1
  69. package/src/composites/upload-artifact.ts +2 -1
@@ -1,44 +1,33 @@
1
1
  /**
2
- * The action versions the github lexicon emits, and the commit each release
3
- * tag points at.
2
+ * The action versions the github lexicon emits. The table is core's
3
+ * (`@intentius/chant/action-pins`, #4403); this file reads it, so an update is
4
+ * one change there (#4410). Each action here is its newest row in core's table,
5
+ * pinned at the commit of its latest release.
4
6
  *
5
- * Composites emit `<slug>@<major>` by default. With `pin: "sha"` they emit
6
- * `<slug>@<sha> # <version>`, the form GHA021 and GHA029 accept and GHA059
7
- * reads the version label from. The serializer writes the `# <version>` part
8
- * as a YAML comment, so GitHub sees only the SHA.
7
+ * Composites emit `<slug>@<sha> # <version>` by default, the form GHA021 and
8
+ * GHA029 accept and GHA059 reads the version label from. The serializer writes
9
+ * the `# <version>` part as a YAML comment, so GitHub sees only the SHA. With
10
+ * `pin: "tag"` they emit the major tag (`actions/checkout@v7`).
9
11
  *
10
- * Refresh a row by resolving the release tag to its commit:
12
+ * Refresh a row in core by resolving the release tag to its commit:
11
13
  *
12
14
  * gh api repos/actions/checkout/commits/v7.0.1 --jq .sha
13
- *
14
- * Keep `major` on a version whose action runs on a Node runtime GitHub still
15
- * supports.
16
15
  */
17
16
  export interface ActionPin {
18
- /** The major tag composites emit by default, such as `v7`. */
17
+ /** The major tag `pin: "tag"` emits, such as `v7`. */
19
18
  major: string;
20
19
  /** The release the SHA belongs to, written as the pin's comment. */
21
20
  version: string;
22
21
  /** The full commit SHA of `version`. */
23
22
  sha: string;
24
23
  }
25
- export declare const ACTION_PINS: {
26
- readonly "actions/checkout": {
27
- readonly major: "v7";
28
- readonly version: "v7.0.1";
29
- readonly sha: "3d3c42e5aac5ba805825da76410c181273ba90b1";
30
- };
31
- readonly "actions/setup-node": {
32
- readonly major: "v7";
33
- readonly version: "v7.0.0";
34
- readonly sha: "820762786026740c76f36085b0efc47a31fe5020";
35
- };
36
- };
37
- export type PinnedAction = keyof typeof ACTION_PINS;
24
+ declare const SLUGS: readonly ["actions/checkout", "actions/setup-node", "actions/setup-go", "actions/setup-python", "actions/cache", "actions/upload-artifact", "actions/download-artifact", "oven-sh/setup-bun", "golangci/golangci-lint-action", "docker/login-action", "docker/setup-buildx-action", "docker/metadata-action", "docker/build-push-action"];
25
+ export type PinnedAction = (typeof SLUGS)[number];
26
+ export declare const ACTION_PINS: Record<PinnedAction, ActionPin>;
38
27
  /**
39
- * How a composite refers to its action: `"tag"` emits the major tag
40
- * (`actions/checkout@v7`), `"sha"` emits the pinned commit with its version
41
- * as a comment (`actions/checkout@3d3c…90b1 # v7.0.1`).
28
+ * How a composite refers to its action: `"sha"` (the default) emits the pinned
29
+ * commit with its version as a comment (`actions/checkout@3d3c…90b1 # v7.0.1`),
30
+ * `"tag"` emits the major tag (`actions/checkout@v7`).
42
31
  */
43
32
  export type ActionPinMode = "tag" | "sha";
44
33
  /** The `uses:` value for a pinned action. */
@@ -50,4 +39,5 @@ export declare function actionPin(slug: string): ActionPin | undefined;
50
39
  * an empty string for any other action.
51
40
  */
52
41
  export declare function pinFixHint(slug: string): string;
42
+ export {};
53
43
  //# sourceMappingURL=action-pins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-pins.d.ts","sourceRoot":"","sources":["../src/action-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,WAAW;;iBAEpB,KAAK,EAAE,IAAI;iBACX,OAAO,EAAE,QAAQ;iBACjB,GAAG,EAAE,0CAA0C;;;iBAG/C,KAAK,EAAE,IAAI;iBACX,OAAO,EAAE,QAAQ;iBACjB,GAAG,EAAE,0CAA0C;;CAEL,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,WAAW,CAAC;AAEpD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,CAAC;AAE1C,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,GAAE,aAAqB,GAAG,MAAM,CAGhF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAI7D;AAOD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C"}
1
+ {"version":3,"file":"action-pins.d.ts","sourceRoot":"","sources":["../src/action-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,QAAA,MAAM,KAAK,YACT,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,yBAAyB,EACzB,2BAA2B,EAC3B,mBAAmB,EACnB,+BAA+B,EAC/B,qBAAqB,EACrB,4BAA4B,EAC5B,wBAAwB,EACxB,0BAA0B,CAClB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAUlD,eAAO,MAAM,WAAW,EAA2D,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAEnH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,CAAC;AAE1C,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,GAAE,aAAqB,GAAG,MAAM,CAGhF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE7D;AAOD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/C"}
@@ -1,41 +1,51 @@
1
1
  /**
2
- * The action versions the github lexicon emits, and the commit each release
3
- * tag points at.
2
+ * The action versions the github lexicon emits. The table is core's
3
+ * (`@intentius/chant/action-pins`, #4403); this file reads it, so an update is
4
+ * one change there (#4410). Each action here is its newest row in core's table,
5
+ * pinned at the commit of its latest release.
4
6
  *
5
- * Composites emit `<slug>@<major>` by default. With `pin: "sha"` they emit
6
- * `<slug>@<sha> # <version>`, the form GHA021 and GHA029 accept and GHA059
7
- * reads the version label from. The serializer writes the `# <version>` part
8
- * as a YAML comment, so GitHub sees only the SHA.
7
+ * Composites emit `<slug>@<sha> # <version>` by default, the form GHA021 and
8
+ * GHA029 accept and GHA059 reads the version label from. The serializer writes
9
+ * the `# <version>` part as a YAML comment, so GitHub sees only the SHA. With
10
+ * `pin: "tag"` they emit the major tag (`actions/checkout@v7`).
9
11
  *
10
- * Refresh a row by resolving the release tag to its commit:
12
+ * Refresh a row in core by resolving the release tag to its commit:
11
13
  *
12
14
  * gh api repos/actions/checkout/commits/v7.0.1 --jq .sha
13
- *
14
- * Keep `major` on a version whose action runs on a Node runtime GitHub still
15
- * supports.
16
15
  */
17
- export const ACTION_PINS = {
18
- "actions/checkout": {
19
- major: "v7",
20
- version: "v7.0.1",
21
- sha: "3d3c42e5aac5ba805825da76410c181273ba90b1",
22
- },
23
- "actions/setup-node": {
24
- major: "v7",
25
- version: "v7.0.0",
26
- sha: "820762786026740c76f36085b0efc47a31fe5020",
27
- },
28
- };
16
+ import { ACTION_PINS as CORE_PINS } from "@intentius/chant/action-pins";
17
+ const SLUGS = [
18
+ "actions/checkout",
19
+ "actions/setup-node",
20
+ "actions/setup-go",
21
+ "actions/setup-python",
22
+ "actions/cache",
23
+ "actions/upload-artifact",
24
+ "actions/download-artifact",
25
+ "oven-sh/setup-bun",
26
+ "golangci/golangci-lint-action",
27
+ "docker/login-action",
28
+ "docker/setup-buildx-action",
29
+ "docker/metadata-action",
30
+ "docker/build-push-action",
31
+ ];
32
+ /** The newest row of core's table for `slug`. */
33
+ function newest(slug) {
34
+ const rows = Object.values(CORE_PINS).filter((p) => p.slug === slug);
35
+ if (rows.length === 0)
36
+ throw new Error(`action-pins: core's table has no row for ${slug}`);
37
+ const row = rows.reduce((a, b) => (Number(b.major.slice(1)) > Number(a.major.slice(1)) ? b : a));
38
+ return { major: row.major, version: row.tag, sha: row.sha };
39
+ }
40
+ export const ACTION_PINS = Object.fromEntries(SLUGS.map((s) => [s, newest(s)]));
29
41
  /** The `uses:` value for a pinned action. */
30
- export function actionRef(slug, pin = "tag") {
42
+ export function actionRef(slug, pin = "sha") {
31
43
  const entry = ACTION_PINS[slug];
32
44
  return pin === "sha" ? `${slug}@${entry.sha} # ${entry.version}` : `${slug}@${entry.major}`;
33
45
  }
34
46
  /** The pin table entry for an action slug, if the lexicon maintains one. */
35
47
  export function actionPin(slug) {
36
- return Object.prototype.hasOwnProperty.call(ACTION_PINS, slug)
37
- ? ACTION_PINS[slug]
38
- : undefined;
48
+ return Object.prototype.hasOwnProperty.call(ACTION_PINS, slug) ? ACTION_PINS[slug] : undefined;
39
49
  }
40
50
  const COMPOSITE_FOR = {
41
51
  "actions/checkout": "Checkout",
@@ -49,5 +59,7 @@ export function pinFixHint(slug) {
49
59
  if (!actionPin(slug))
50
60
  return "";
51
61
  const pinned = slug;
52
- return ` Use \`uses: ${actionRef(pinned, "sha")}\`, which ${COMPOSITE_FOR[pinned]}({ pin: "sha" }) emits.`;
62
+ const composite = COMPOSITE_FOR[pinned];
63
+ const how = composite ? `, which ${composite}({ pin: "sha" }) emits` : "";
64
+ return ` Use \`uses: ${actionRef(pinned, "sha")}\`${how}.`;
53
65
  }
@@ -9,8 +9,11 @@
9
9
  */
10
10
  import type { OpTrigger } from "@intentius/chant/lexicon";
11
11
  /** The artifact actions every generated job uses, at the major the generators have always pinned. */
12
- export declare const UPLOAD_ARTIFACT = "actions/upload-artifact@v4";
13
- export declare const DOWNLOAD_ARTIFACT = "actions/download-artifact@v4";
12
+ export declare const UPLOAD_ARTIFACT: string;
13
+ export declare const DOWNLOAD_ARTIFACT: string;
14
+ export declare const CHECKOUT: string;
15
+ export declare const CACHE_RESTORE: string;
16
+ export declare const CACHE_SAVE: string;
14
17
  /** The checkout step; `fullHistory` fetches every commit, for a job that reads the base commit or measures a change with git. */
15
18
  export declare function checkoutStep(fullHistory?: boolean): Record<string, unknown>;
16
19
  /** One `run:` step per line, so each line is its own step in the log. */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D,qGAAqG;AACrG,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAC5D,eAAO,MAAM,iBAAiB,iCAAiC,CAAC;AAEhE,iIAAiI;AACjI,wBAAgB,YAAY,CAAC,WAAW,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEjF;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1G;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOpG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5F;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,EAAE,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAW/D"}
1
+ {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D,qGAAqG;AACrG,eAAO,MAAM,eAAe,QAAgC,CAAC;AAC7D,eAAO,MAAM,iBAAiB,QAAkC,CAAC;AACjE,eAAO,MAAM,QAAQ,QAA0B,CAAC;AAChD,eAAO,MAAM,aAAa,QAA8B,CAAC;AACzD,eAAO,MAAM,UAAU,QAA2B,CAAC;AAEnD,iIAAiI;AACjI,wBAAgB,YAAY,CAAC,WAAW,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEjF;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1G;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOpG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5F;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,EAAE,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAW/D"}
@@ -9,13 +9,16 @@
9
9
  */
10
10
  import { emitYAML, emitYAMLEntry } from "@intentius/chant/yaml";
11
11
  import { BUILD_REQUEST_TAGS, GITHUB_JOB_TOKEN, pushBranches } from "@intentius/chant/ci-jobs";
12
- import { actionRef } from "../action-pins.js";
12
+ import { ACTION_PINS as PINS, tagComments, usesRef } from "@intentius/chant/action-pins";
13
13
  /** The artifact actions every generated job uses, at the major the generators have always pinned. */
14
- export const UPLOAD_ARTIFACT = "actions/upload-artifact@v4";
15
- export const DOWNLOAD_ARTIFACT = "actions/download-artifact@v4";
14
+ export const UPLOAD_ARTIFACT = usesRef(PINS.uploadArtifact4);
15
+ export const DOWNLOAD_ARTIFACT = usesRef(PINS.downloadArtifact4);
16
+ export const CHECKOUT = usesRef(PINS.checkout7);
17
+ export const CACHE_RESTORE = usesRef(PINS.cacheRestore4);
18
+ export const CACHE_SAVE = usesRef(PINS.cacheSave4);
16
19
  /** The checkout step; `fullHistory` fetches every commit, for a job that reads the base commit or measures a change with git. */
17
20
  export function checkoutStep(fullHistory = false) {
18
- return fullHistory ? { uses: actionRef("actions/checkout"), with: { "fetch-depth": 0 } } : { uses: actionRef("actions/checkout") };
21
+ return fullHistory ? { uses: CHECKOUT, with: { "fetch-depth": 0 } } : { uses: CHECKOUT };
19
22
  }
20
23
  /** One `run:` step per line, so each line is its own step in the log. */
21
24
  export function runSteps(lines) {
@@ -106,5 +109,5 @@ export function emitWorkflowYAML(doc) {
106
109
  if (doc.permissions && Object.keys(doc.permissions).length > 0)
107
110
  sections.push(emitYAMLEntry("permissions", doc.permissions));
108
111
  sections.push(emitYAMLEntry("jobs", doc.jobsDoc));
109
- return sections.join("\n\n") + "\n";
112
+ return tagComments(sections.join("\n\n") + "\n");
110
113
  }
@@ -124,6 +124,13 @@ export interface GithubOpPipelineDoc {
124
124
  * artifact instead (#2256).
125
125
  */
126
126
  gatedNoticeDoc?: Record<string, unknown>;
127
+ /**
128
+ * The review note job (#4326), when the spec sets `reviewAgent`. Kept out
129
+ * of {@link jobsDoc} for the reason the gate notice is: a member's
130
+ * `working-directory` default belongs to the job that runs the Op, and a
131
+ * dialect transforms this job on its own terms.
132
+ */
133
+ reviewNoteDoc?: Record<string, unknown>;
127
134
  }
128
135
  /** One generated file: a suggested name plus its pipeline document, pre-emission. */
129
136
  export interface GithubOpPipelineFile {
@@ -1 +1 @@
1
- {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAOhB,gBAAgB,EAIjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAEnD,aAAa,EACb,gBAAgB,IAAI,sBAAsB,EAG1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAIhE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,qFAAqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,mBAAmB,CAAC;CAC1B;AAuOD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC;IAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,CA8F1D;AAwCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAMnE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAMxB"}
1
+ {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAOhB,gBAAgB,EAIjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAEnD,aAAa,EACb,gBAAgB,IAAI,sBAAsB,EAG1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAIhE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,qFAAqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,mBAAmB,CAAC;CAC1B;AAuOD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC;IAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,CAoH1D;AAwCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAMnE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAMxB"}
@@ -73,6 +73,7 @@ import { readFileSync } from "node:fs";
73
73
  import { assertEnvironment, assertSetupSteps, CI_IMAGE, ciJobName, environmentDoc, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER, mergePermissions, nonEmpty, permissionsFor, setupStepDoc, } from "@intentius/chant/ci-jobs";
74
74
  import { memberPathFilter, resolveOpTrigger } from "@intentius/chant/lexicon";
75
75
  import { checkoutStep, concurrency, emitWorkflowYAML, jobTokenEnv, onTrigger, opConcurrencyGroup, runSteps } from "./ci-jobs.js";
76
+ import { assertReviewSplit, reviewJobs, reviewNoteJobName } from "./review-jobs.js";
76
77
  // The setup, environment and permission checks are shared with the Op waves
77
78
  // generator and the forgejo lexicon (#3765); they stay importable from here.
78
79
  export { assertEnvironment, assertSetupSteps, mergePermissions };
@@ -316,7 +317,11 @@ export function buildGithubOpPipelineDocs(ops, options = {}) {
316
317
  const runParts = fillCommand(runCommand, spec.name);
317
318
  // A live-resolution read (rate limits) always benefits from a token;
318
319
  // creating an issue/PR additionally needs `gh` CLI's own token variable.
319
- const stepEnv = jobTokenEnv(findingMode !== "report");
320
+ // A split review (#4326) runs the change's code beside a model: no step of that job holds a forge token.
321
+ const split = spec.reviewAgent === true;
322
+ if (split)
323
+ assertReviewSplit(spec.name, trigger, findingMode, spec.permissions);
324
+ const stepEnv = split ? {} : jobTokenEnv(findingMode !== "report");
320
325
  const setup = spec.setup ?? [];
321
326
  assertSetupSteps(spec.name, setup);
322
327
  if (spec.environment)
@@ -334,6 +339,19 @@ export function buildGithubOpPipelineDocs(ops, options = {}) {
334
339
  ? gatedRunStep(RUN_STEP_ID, spec.name, invocation, stepEnv)
335
340
  : { run: invocation, env: stepEnv });
336
341
  steps.push(...runSteps(extraScript));
342
+ const review = split
343
+ ? reviewJobs({
344
+ name: spec.name,
345
+ jobName,
346
+ image,
347
+ invocation,
348
+ setup: setup.map(setupStepDoc),
349
+ beforeScript,
350
+ extraScript,
351
+ ...(spec.variables ? { variables: spec.variables } : {}),
352
+ ...(options.member ? { member: options.member } : {}),
353
+ })
354
+ : undefined;
337
355
  const doc = {
338
356
  name: spec.name,
339
357
  on: onTrigger(trigger),
@@ -369,6 +387,11 @@ export function buildGithubOpPipelineDocs(ops, options = {}) {
369
387
  },
370
388
  ...(gated ? { gatedNoticeDoc: { [`${jobName}-gate-notice`]: gateNoticeJob(jobName, image) } } : {}),
371
389
  };
390
+ // A split review (#4326): the Op's job is the review job, and the note job rides beside it.
391
+ if (review) {
392
+ doc.jobsDoc = { [jobName]: { ...review.review, ...(spec.environment ? { environment: environmentDoc(spec.environment) } : {}) } };
393
+ doc.reviewNoteDoc = { [reviewNoteJobName(jobName)]: review.note };
394
+ }
372
395
  files.push({ name: `${spec.name}.yml`, doc });
373
396
  }
374
397
  if (options.member) {
@@ -422,8 +445,8 @@ export function emitOpPipelineYAML(doc) {
422
445
  // The gated-apply notice job rides in `jobs:` beside the Op's own job; it
423
446
  // is carried separately on the doc so a dialect transforms it on its own.
424
447
  // A dialect's `header` note (#2257) is absent on github.
425
- const { gatedNoticeDoc, ...workflow } = doc;
426
- return emitWorkflowYAML({ ...workflow, jobsDoc: { ...doc.jobsDoc, ...(gatedNoticeDoc ?? {}) } });
448
+ const { gatedNoticeDoc, reviewNoteDoc, ...workflow } = doc;
449
+ return emitWorkflowYAML({ ...workflow, jobsDoc: { ...doc.jobsDoc, ...(gatedNoticeDoc ?? {}), ...(reviewNoteDoc ?? {}) } });
427
450
  }
428
451
  /**
429
452
  * Synthesize one `.github/workflows/*.yml` per scheduled Op. Thin wrapper
@@ -3,9 +3,17 @@
3
3
  *
4
4
  * One workflow for the whole spec, on a push to its branches (and by hand),
5
5
  * one run at a time. Each job is one of core's `opWaveJobs`: a wave, or a wide
6
- * wave's deciding job and share jobs, chained by `needs:` so a wave that waits
7
- * at its gate (exit 3) or fails stops every later wave. Re-running the failed
8
- * jobs after `chant approve` carries on from that wave.
6
+ * wave's deciding job and share jobs, chained by `needs:`. Re-running the
7
+ * failed jobs after `chant approve` carries on from that wave.
8
+ *
9
+ * Each job keeps its outcome file as an artifact whatever its exit code
10
+ * (#4322), and a job with `needs:` downloads every artifact of the run into
11
+ * the outcome directory first. A wave or deciding job after the first runs
12
+ * unless the run was cancelled (`if: !cancelled()`), so it runs after a
13
+ * failure in the wave before: it blocks the runs that depend on a failed one
14
+ * and applies the rest, and it stops by itself when an earlier wave waits at
15
+ * its gate (exit 3) or applied nothing for another reason. A share job still
16
+ * needs its deciding job to succeed.
9
17
  *
10
18
  * Every job checks out the full history, because the runner reads the gate
11
19
  * policy from the spec file at the base commit and records pending gates on
@@ -49,6 +57,8 @@ import { type GithubOpPipelineDoc } from "./generate-op-pipeline.js";
49
57
  export declare const OP_WAVES_IMAGE = "node:22";
50
58
  /** The artifact a wide wave's deciding job hands its share jobs. */
51
59
  export declare function opWaveRecordArtifact(spec: Pick<OpWavesSpec, "name">, wave: number): string;
60
+ /** The artifact a job keeps its outcome file in (#4322). */
61
+ export declare function opWaveOutcomeArtifact(spec: Pick<OpWavesSpec, "name">, jobName: string): string;
52
62
  /** The workflow document, before emission, and the jobs it holds. */
53
63
  export declare function buildGithubOpWavesDoc(spec: OpWavesSpec, options: OpWavesPipelineOptions): {
54
64
  doc: GithubOpPipelineDoc;
@@ -1 +1 @@
1
- {"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAoE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAClI,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAiB9F,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,yFAAyF;AACzF,eAAO,MAAM,cAAc,YAAW,CAAC;AAEvC,oEAAoE;AACpE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,sBAAsB,GAC9B;IAAE,GAAG,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAgEpI;AAoDD,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAMvH"}
1
+ {"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,EAAsF,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACpJ,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAiB9F,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,yFAAyF;AACzF,eAAO,MAAM,cAAc,YAAW,CAAC;AAEvC,oEAAoE;AACpE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9F;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,sBAAsB,GAC9B;IAAE,GAAG,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAuEpI;AAoDD,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAMvH"}
@@ -3,9 +3,17 @@
3
3
  *
4
4
  * One workflow for the whole spec, on a push to its branches (and by hand),
5
5
  * one run at a time. Each job is one of core's `opWaveJobs`: a wave, or a wide
6
- * wave's deciding job and share jobs, chained by `needs:` so a wave that waits
7
- * at its gate (exit 3) or fails stops every later wave. Re-running the failed
8
- * jobs after `chant approve` carries on from that wave.
6
+ * wave's deciding job and share jobs, chained by `needs:`. Re-running the
7
+ * failed jobs after `chant approve` carries on from that wave.
8
+ *
9
+ * Each job keeps its outcome file as an artifact whatever its exit code
10
+ * (#4322), and a job with `needs:` downloads every artifact of the run into
11
+ * the outcome directory first. A wave or deciding job after the first runs
12
+ * unless the run was cancelled (`if: !cancelled()`), so it runs after a
13
+ * failure in the wave before: it blocks the runs that depend on a failed one
14
+ * and applies the rest, and it stops by itself when an earlier wave waits at
15
+ * its gate (exit 3) or applied nothing for another reason. A share job still
16
+ * needs its deciding job to succeed.
9
17
  *
10
18
  * Every job checks out the full history, because the runner reads the gate
11
19
  * policy from the spec file at the base commit and records pending gates on
@@ -42,10 +50,10 @@
42
50
  * token is requested with, so the code the job runs holds no OIDC token
43
51
  * and cannot request one for another group's identity.
44
52
  */
45
- import { notifyVariables, opWaveGroupOf, opWaveJobs, opWavesResumeCommand } from "@intentius/chant/op/op-waves";
53
+ import { notifyVariables, opWaveGroupOf, opWaveJobs, opWaveOutcomeDir, opWavesResumeCommand } from "@intentius/chant/op/op-waves";
46
54
  import { assertEnvironment, assertSetupSteps, CI_IMAGE, dirnameOf, environmentDoc, GITHUB_JOB_TOKEN, githubRunsOn, githubSecretEnv, idTokenAudienceEnv, nonEmpty, pushBranches, setupStepDoc, } from "@intentius/chant/ci-jobs";
47
55
  import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
48
- import { checkoutStep, concurrency, downloadArtifactStep, keepRecordStep, runSteps } from "./ci-jobs.js";
56
+ import { checkoutStep, concurrency, DOWNLOAD_ARTIFACT, downloadArtifactStep, keepRecordStep, runSteps } from "./ci-jobs.js";
49
57
  import { emitOpPipelineYAML } from "./generate-op-pipeline.js";
50
58
  /** The default image: it carries git, which the runner needs to read the base commit. */
51
59
  export const OP_WAVES_IMAGE = CI_IMAGE;
@@ -53,6 +61,10 @@ export const OP_WAVES_IMAGE = CI_IMAGE;
53
61
  export function opWaveRecordArtifact(spec, wave) {
54
62
  return `${spec.name}-wave-${wave}-decision`;
55
63
  }
64
+ /** The artifact a job keeps its outcome file in (#4322). */
65
+ export function opWaveOutcomeArtifact(spec, jobName) {
66
+ return `${spec.name}-${jobName}-outcome`;
67
+ }
56
68
  /** The workflow document, before emission, and the jobs it holds. */
57
69
  export function buildGithubOpWavesDoc(spec, options) {
58
70
  const jobs = opWaveJobs(spec, options.specFile);
@@ -80,6 +92,10 @@ export function buildGithubOpWavesDoc(spec, options) {
80
92
  if (job.kind === "share") {
81
93
  steps.push(downloadArtifactStep(`Download wave ${job.wave}'s decision`, opWaveRecordArtifact(spec, job.wave), dirnameOf(job.record)));
82
94
  }
95
+ if (job.needs.length > 0) {
96
+ // #4322: every artifact of the run, so the job reads each earlier job's outcome file.
97
+ steps.push({ name: "Download the earlier jobs' outcomes", uses: DOWNLOAD_ARTIFACT, with: { path: opWaveOutcomeDir(spec) } });
98
+ }
83
99
  const later = [];
84
100
  later.push(...[...(wave.setup ?? []), ...(group?.setup ?? [])].map(setupStepDoc), ...runSteps(options.beforeScript ?? []));
85
101
  later.push({ name: describeJob(job.kind, job.wave, wave.name, job.share, job.group), run: job.command.join(" ") });
@@ -88,12 +104,15 @@ export function buildGithubOpWavesDoc(spec, options) {
88
104
  if (job.kind === "decide") {
89
105
  steps.push(keepRecordStep(`Keep wave ${job.wave}'s decision`, opWaveRecordArtifact(spec, job.wave), job.record));
90
106
  }
107
+ steps.push(keepRecordStep(`Keep the job's outcome`, opWaveOutcomeArtifact(spec, job.jobName), job.outcome));
91
108
  const environment = group?.environment ?? wave.environment;
92
109
  const idTokens = group?.idTokens ?? wave.idTokens;
93
110
  const idToken = idTokens !== undefined || cloud !== undefined;
94
111
  jobsDoc[job.jobName] = {
95
112
  "runs-on": githubRunsOn(group?.runner ?? wave.runner ?? spec.runner),
96
113
  ...(job.needs.length > 0 ? { needs: job.needs } : {}),
114
+ // #4322: a wave's jobs run after a failure in the wave before; a share job runs only on its deciding job's success.
115
+ ...(job.needs.length > 0 && job.kind !== "share" ? { if: "!cancelled()" } : {}),
97
116
  container: image,
98
117
  ...(environment ? { environment: environmentDoc(environment) } : {}),
99
118
  // #3914: a job's own permissions replace the workflow's, so they repeat them.
@@ -1 +1 @@
1
- {"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AA4BjG,OAAO,KAAK,EACV,oBAAoB,IAAI,YAAY,EACpC,wBAAwB,IAAI,qBAAqB,EACjD,uBAAuB,IAAI,oBAAoB,EAEhD,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,mIAAmI;IACnI,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAmcD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAWpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CA+JnB;AA4CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAGtB"}
1
+ {"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAgCjG,OAAO,KAAK,EACV,oBAAoB,IAAI,YAAY,EACpC,wBAAwB,IAAI,qBAAqB,EACjD,uBAAuB,IAAI,oBAAoB,EAEhD,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,mIAAmI;IACnI,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAgfD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAWpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CA+JnB;AA4CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAGtB"}
@@ -61,10 +61,9 @@ import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/ga
61
61
  import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
62
62
  import { CHECKOUT_TOKEN_ENV, checkoutScript, dropForgeTokens, handoverOutputs, handoverTrap, takeOverScript } from "@intentius/chant/ci-handover";
63
63
  import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
64
- import { APPLY_COMMENT, GREEN_TAG_PATTERN, GREEN_TICK_WORKFLOW, PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prApplyGroup, prRecordGroup, prApplyRecordKey, prApplyWaveJobName, prLoopJobs, prLoopPostJob, prLoopSetup, PR_PLAN_NOTE_JOB, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
64
+ import { APPLY_COMMENT, GREEN_TAG_PATTERN, GREEN_TICK_WORKFLOW, PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prApplyGroup, prRecordGroup, prApplyRecordKey, prApplyWaveJobName, prLoopJobs, prLoopPostJob, prLoopSetup, PR_PLAN_NOTE_JOB, PR_LOCK_JOB, PR_UNLOCK_JOB, PR_UNLOCK_COMMAND, prLockCommand, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
65
65
  import { memberRepoPath } from "@intentius/chant/lexicon";
66
- import { afterEarlierWave, checkoutStep, concurrency, downloadArtifactStep, emitWorkflowYAML, keepRecordStep, runSteps, UPLOAD_ARTIFACT } from "./ci-jobs.js";
67
- import { actionRef } from "../action-pins.js";
66
+ import { afterEarlierWave, CACHE_RESTORE, CACHE_SAVE, CHECKOUT, checkoutStep, concurrency, downloadArtifactStep, emitWorkflowYAML, keepRecordStep, runSteps, UPLOAD_ARTIFACT } from "./ci-jobs.js";
68
67
  /** The plan job's last step, whose outputs carry its report to the plan-note job (#4295). */
69
68
  const PLAN_STEP_ID = "plan";
70
69
  /** The output a gated apply wave hands the green commit it applies through, and the step that sets it (#4221). */
@@ -185,13 +184,13 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
185
184
  const recordKey = (wave) => `${recordPrefix}\${{ github.run_id }}-\${{ github.run_attempt }}${wave ? `-wave-${wave}` : ""}`;
186
185
  const restoreRecord = (wave) => ({
187
186
  name: wave ? "Restore the newest record of this commit's apply" : "Restore the record of an earlier attempt at this commit",
188
- uses: "actions/cache/restore@v4",
187
+ uses: CACHE_RESTORE,
189
188
  with: { path: recordPath, key: recordKey(wave), "restore-keys": recordPrefix },
190
189
  });
191
190
  const saveRecord = (wave) => ({
192
191
  name: wave ? "Keep the record for the next wave and a re-run" : "Keep the record for a re-run",
193
192
  if: "always()",
194
- uses: "actions/cache/save@v4",
193
+ uses: CACHE_SAVE,
195
194
  with: { path: recordPath, key: recordKey(wave) },
196
195
  });
197
196
  // #4296: the step that trades the job's OIDC token for its cloud identity, before the checkout.
@@ -295,7 +294,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
295
294
  const post = prLoopPostJob(forgeName, env, member?.name);
296
295
  const noteSteps = [
297
296
  // The target branch's commit: the chant this job runs is the target branch's, and none of the change's code is here.
298
- { name: "Check out the target branch's commit", uses: actionRef("actions/checkout"), with: { ref: "${{ github.event.pull_request.base.sha }}" } },
297
+ { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}" } },
299
298
  ...runSteps(beforeScript),
300
299
  { name: "Take the plan report from the plan job", run: takeOverScript("plan", PR_LOOP_REPORT_DIR, forgeName) },
301
300
  { name: "Keep the plan report", if: "always()", uses: UPLOAD_ARTIFACT, with: { name: reportName(plan), path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true } },
@@ -303,9 +302,46 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
303
302
  ];
304
303
  // #4298: with a pull request environment the pull_request trigger fires on close too, and the plan skips that event.
305
304
  const prEnv = loop.prEnvironment ? prEnvGithubJobs(loop.forge ?? "github", image, beforeScript, extraScript, loop, member) : undefined;
305
+ // #4327: with ci.locks: plan the pull_request trigger fires on close too, for the unlock job, and the plan skips that event.
306
+ const locks = loop.locks === "plan";
307
+ const closes = Boolean(prEnv) || locks;
308
+ const lockJobs = locks
309
+ ? {
310
+ [PR_LOCK_JOB]: {
311
+ // The plan job's outputs carry the report; a plan that was skipped or failed leaves this job skipped.
312
+ needs: ["plan"],
313
+ if: "github.event_name == 'pull_request'",
314
+ "runs-on": GITHUB_RUNNER,
315
+ container: image,
316
+ permissions: { contents: "write", "pull-requests": "read", statuses: "write" },
317
+ steps: [
318
+ // The target branch's commit: this job pushes chant/lifecycle, so none of the change's code runs in it.
319
+ { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
320
+ ...runSteps(beforeScript),
321
+ { name: "Take the plan report from the plan job", run: takeOverScript("plan", PR_LOOP_REPORT_DIR, forgeName) },
322
+ {
323
+ name: "Lock the components the plan selected",
324
+ env: { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.event.pull_request.head.sha }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN },
325
+ run: prLockCommand(forgeName),
326
+ },
327
+ ],
328
+ },
329
+ [PR_UNLOCK_JOB]: {
330
+ if: "github.event_name == 'pull_request' && github.event.action == 'closed'",
331
+ "runs-on": GITHUB_RUNNER,
332
+ container: image,
333
+ permissions: { contents: "write" },
334
+ steps: [
335
+ { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
336
+ ...runSteps(beforeScript),
337
+ { name: "Release the pull request's locks", env: { PR_NUMBER: "${{ github.event.pull_request.number }}" }, run: PR_UNLOCK_COMMAND },
338
+ ],
339
+ },
340
+ }
341
+ : {};
306
342
  const jobsDoc = {
307
343
  plan: {
308
- if: prEnv ? "github.event_name == 'pull_request' && github.event.action != 'closed'" : "github.event_name == 'pull_request'",
344
+ if: closes ? "github.event_name == 'pull_request' && github.event.action != 'closed'" : "github.event_name == 'pull_request'",
309
345
  "runs-on": GITHUB_RUNNER,
310
346
  container: image,
311
347
  permissions: withIdToken({ contents: "read" }),
@@ -321,6 +357,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
321
357
  permissions: { actions: "read", contents: "read", "pull-requests": "write", statuses: "write" },
322
358
  steps: noteSteps,
323
359
  },
360
+ ...lockJobs,
324
361
  ...Object.fromEntries(applyJobs),
325
362
  ...(prEnv ? prEnv.jobs : {}),
326
363
  };
@@ -330,6 +367,8 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
330
367
  : forgejo
331
368
  ? { pull_request: { branches: [branch] }, push: { branches: [branch], tags: [GREEN_TAG_PATTERN] } }
332
369
  : { pull_request: { branches: [branch] }, push: { branches: [branch] }, workflow_run: { workflows: [GREEN_TICK_WORKFLOW], types: ["completed"] }, workflow_dispatch: {} };
370
+ if (closes)
371
+ on.pull_request = { branches: [branch], types: ["opened", "synchronize", "reopened", "closed"] };
333
372
  if (prEnv) {
334
373
  on.pull_request = { branches: [branch], types: ["opened", "synchronize", "reopened", "closed"] };
335
374
  on.schedule = [{ cron: PR_ENV_SWEEP_CRON }];
@@ -344,10 +383,16 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
344
383
  ...(rooted ? {} : { defaults: { run: { "working-directory": member.dir } } }),
345
384
  env: { ...options.variables, CHANT_ENV: env },
346
385
  jobsDoc,
347
- stages: ["plan", PR_PLAN_NOTE_JOB, ...applyNames, ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
386
+ stages: ["plan", PR_PLAN_NOTE_JOB, ...(locks ? [PR_LOCK_JOB, PR_UNLOCK_JOB] : []), ...applyNames, ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
348
387
  jobs: [
349
388
  { jobName: "plan", component: "pull request plan", stage: "plan", needs: [] },
350
389
  { jobName: PR_PLAN_NOTE_JOB, component: "pull request plan note", stage: PR_PLAN_NOTE_JOB, needs: ["plan"] },
390
+ ...(locks
391
+ ? [
392
+ { jobName: PR_LOCK_JOB, component: "pull request lock", stage: PR_LOCK_JOB, needs: ["plan"] },
393
+ { jobName: PR_UNLOCK_JOB, component: "pull request unlock", stage: PR_UNLOCK_JOB, needs: [] },
394
+ ]
395
+ : []),
351
396
  ...applyNames.map((name, i) => ({
352
397
  jobName: name,
353
398
  component: name === "record" ? "merge record" : loop.applyBeforeMerge ? "apply before merge" : loop.waveGate ? `green apply, wave ${i}` : "green apply",
@@ -426,7 +471,7 @@ function prEnvGithubJobs(forge, image, beforeScript, extraScript, loop, member)
426
471
  permissions: writes,
427
472
  steps: [
428
473
  // The target branch's commit: the chant this job runs is the target branch's, and none of the change's code is here.
429
- { name: "Check out the target branch's commit", uses: actionRef("actions/checkout"), with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
474
+ { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
430
475
  ...runSteps(beforeScript),
431
476
  { name: "Take the environment's report from the env-up job", run: takeOverScript("env-up", PR_ENV_REPORT_DIR, forge) },
432
477
  keep("env-up"),
@@ -441,7 +486,7 @@ function prEnvGithubJobs(forge, image, beforeScript, extraScript, loop, member)
441
486
  permissions: withIdToken(writes),
442
487
  steps: [
443
488
  ...tradeStep,
444
- { uses: actionRef("actions/checkout"), with: { "fetch-depth": 0, ...(ref ? { ref } : {}) } },
489
+ { uses: CHECKOUT, with: { "fetch-depth": 0, ...(ref ? { ref } : {}) } },
445
490
  ...runSteps(prLoopSetup(image)),
446
491
  ...runSteps(beforeScript),
447
492
  { name, env: j.env, run: j.command },