@intentius/chant-lexicon-github 0.124.56 → 0.124.57

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.
@@ -104,8 +104,8 @@ export interface GithubOpPipelineDoc {
104
104
  on: Record<string, unknown>;
105
105
  /** The `env:` mapping, when `options.variables` is set. */
106
106
  env?: Record<string, unknown>;
107
- /** The `concurrency:` mapping — one run at a time per Op. */
108
- concurrency: Record<string, unknown>;
107
+ /** The `concurrency:` mapping — one run at a time per Op. Absent on a waves file (ruling for ci-products 2026-10-28). */
108
+ concurrency?: Record<string, unknown>;
109
109
  /**
110
110
  * The `permissions:` mapping for this Op's finding-mode. GitHub-only:
111
111
  * Forgejo Actions ignores `permissions:` entirely, so the forgejo dialect
@@ -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;AAUlC,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;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;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,EACrC,KAAK,GAAE,QAAQ,GAAG,SAAoB,GACrC;IAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,CAuM1D;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;AAUlC,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,yHAAyH;IACzH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC;;;;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;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;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,EACrC,KAAK,GAAE,QAAQ,GAAG,SAAoB,GACrC;IAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,CAuM1D;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"}
@@ -513,14 +513,14 @@ function scopeOpFileToMember(file, member) {
513
513
  const { steps, ...rest } = props;
514
514
  return [job, { ...rest, defaults: { run: { "working-directory": member.dir } }, steps }];
515
515
  }));
516
- const group = file.doc.concurrency.group;
516
+ const group = file.doc.concurrency?.group;
517
517
  return {
518
518
  name,
519
519
  doc: {
520
520
  ...file.doc,
521
521
  ...(file.doc.name ? { name: `${member.name}/${file.doc.name}` } : {}),
522
522
  on,
523
- concurrency: { ...file.doc.concurrency, ...(typeof group === "string" ? { group: `${member.name}-${group}` } : {}) },
523
+ ...(file.doc.concurrency ? { concurrency: { ...file.doc.concurrency, ...(typeof group === "string" ? { group: `${member.name}-${group}` } : {}) } } : {}),
524
524
  jobsDoc,
525
525
  },
526
526
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Op waves → GitHub Actions workflow (#3679).
3
3
  *
4
- * One workflow for the whole spec, on a push to its branches (and by hand),
5
- * one run at a time. Each job is one of core's `opWaveJobs`: a wave, or a wide
4
+ * One workflow for the whole spec, on a push to its branches (and by hand).
5
+ * Each job is one of core's `opWaveJobs`: a wave, or a wide
6
6
  * wave's deciding job and share jobs, chained by `needs:`. Re-running the
7
7
  * failed jobs after `chant approve` carries on from that wave.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAmB9F,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,EAC/B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAO,GAC7C;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,CA4FpI;AAoID,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,OAAO,EAaL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAmB9F,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,EAC/B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAO,GAC7C;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,CAgGpI;AAoID,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAMvH"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Op waves → GitHub Actions workflow (#3679).
3
3
  *
4
- * One workflow for the whole spec, on a push to its branches (and by hand),
5
- * one run at a time. Each job is one of core's `opWaveJobs`: a wave, or a wide
4
+ * One workflow for the whole spec, on a push to its branches (and by hand).
5
+ * Each job is one of core's `opWaveJobs`: a wave, or a wide
6
6
  * wave's deciding job and share jobs, chained by `needs:`. Re-running the
7
7
  * failed jobs after `chant approve` carries on from that wave.
8
8
  *
@@ -74,7 +74,7 @@
74
74
  * forgejo generator passes `forge: "forgejo"`, which checks the ref and
75
75
  * runner only.
76
76
  */
77
- import { notifyVariables, opWaveGroupOf, opWaveJobs, opWaveOutcomeDir, opWavesControlTarget, opWavesLastJobs, opWavesApprovalStatusCommand, opWavesPostsApprovalStatus, opWavesOutcomeCommand, opWavesRecordPlansCommand, opWavesResumeCommand, } from "@intentius/chant/op/op-waves";
77
+ import { notifyVariables, opWaveGroupOf, opWaveJobs, opWaveJobTimeout, opWaveOutcomeDir, opWavesControlTarget, opWavesLastJobs, opWavesApprovalStatusCommand, opWavesPostsApprovalStatus, opWavesOutcomeCommand, opWavesRecordPlansCommand, opWavesResumeCommand, } from "@intentius/chant/op/op-waves";
78
78
  import { CONTROL_OUTCOME_VAR, controlDispatchJob } from "@intentius/chant/control/dispatch";
79
79
  import { assertEnvironment, assertSetupSteps, CI_IMAGE, dirnameOf, environmentDoc, GITHUB_JOB_TOKEN, githubRunsOn, githubSecretEnv, idTokenAudienceEnv, nonEmpty, pushBranches, setupStepDoc, } from "@intentius/chant/ci-jobs";
80
80
  import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
@@ -112,6 +112,8 @@ export function buildGithubOpWavesDoc(spec, options, dialect = {}) {
112
112
  }
113
113
  }
114
114
  const permissions = { contents: "write", ...(byReview ? { "pull-requests": "read" } : {}) };
115
+ // The ruling for ci-products (2026-10-28): every wave job carries its timeout and the lock wait under it.
116
+ const timeout = opWaveJobTimeout(options.jobTimeoutMinutes);
115
117
  const jobsDoc = {};
116
118
  for (const job of jobs) {
117
119
  const wave = spec.waves[job.wave - 1];
@@ -159,7 +161,8 @@ export function buildGithubOpWavesDoc(spec, options, dialect = {}) {
159
161
  ...(environment ? { environment: environmentDoc(environment) } : {}),
160
162
  // #3914: a job's own permissions replace the workflow's, so they repeat them.
161
163
  ...(idToken ? { permissions: { ...permissions, "id-token": "write" } } : {}),
162
- ...nonEmpty("env", group || idTokens ? { ...wave.variables, ...group?.variables, ...idTokenAudienceEnv(idTokens) } : wave.variables),
164
+ "timeout-minutes": timeout.minutes,
165
+ env: { ...timeout.env, ...(group || idTokens ? { ...wave.variables, ...group?.variables, ...idTokenAudienceEnv(idTokens) } : wave.variables) },
163
166
  steps,
164
167
  };
165
168
  }
@@ -174,7 +177,8 @@ export function buildGithubOpWavesDoc(spec, options, dialect = {}) {
174
177
  name: spec.name,
175
178
  on: { push: { branches: pushBranches(spec.branches) }, workflow_dispatch: {} },
176
179
  ...nonEmpty("env", env),
177
- concurrency: concurrency(spec.name),
180
+ // The ruling for ci-products (2026-10-28): no workflow-level concurrency group. Runs on the target and repo grains wait for
181
+ // their rows on chant's ledger, oldest commit first; plunge stacks on the resource grain lease their resource rows and run.
178
182
  permissions,
179
183
  jobsDoc,
180
184
  };
@@ -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;AA2CjG,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;AAgoBD;;;;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;AA2CjG,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;AAkoBD;;;;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,7 +61,7 @@ import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/ga
61
61
  import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER, githubRunsOn } 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 { prApplyComment, 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, prCommentLockJobs, prCommentPreviewJob, PREVIEW_REF_ENV, PREVIEW_HEAD_OUTPUT, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, prLoopJobStage, prStaleJob, PR_STALE_JOB, prLoopRunner, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
64
+ import { prApplyComment, 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, prUnlockCommand, prLockCommand, prCommentLockJobs, prCommentPreviewJob, PREVIEW_REF_ENV, PREVIEW_HEAD_OUTPUT, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, prLoopJobStage, prStaleJob, PR_STALE_JOB, prLoopRunner, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
65
65
  import { forgejoRepoLockLines, REPO_APPLY_GROUP } from "@intentius/chant/components/repo-lock";
66
66
  import { memberRepoPath } from "@intentius/chant/lexicon";
67
67
  import { productStatusContext, resolveProductWords } from "@intentius/chant/ci-product-words";
@@ -356,6 +356,8 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
356
356
  const prEnv = loop.prEnvironment ? prEnvGithubJobs(loop.forge ?? "github", image, beforeScript, extraScript, loop, member) : undefined;
357
357
  // #4327: with ci.locks: plan the pull_request trigger fires on close too, for the unlock job, and the plan skips that event.
358
358
  const locks = loop.locks === "plan";
359
+ // plywood#22: the lock lines run through the product's bin, as its other job lines do.
360
+ const lockBin = resolveProductWords(loop.words).bin;
359
361
  const closes = Boolean(prEnv) || locks;
360
362
  const lockJobs = locks
361
363
  ? {
@@ -374,7 +376,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
374
376
  {
375
377
  name: "Lock the components the plan selected",
376
378
  env: { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.event.pull_request.head.sha }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN },
377
- run: prLockCommand(forgeName),
379
+ run: prLockCommand(forgeName, lockBin),
378
380
  },
379
381
  ],
380
382
  },
@@ -386,7 +388,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
386
388
  steps: [
387
389
  { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
388
390
  ...runSteps(beforeScript),
389
- { name: "Release the pull request's locks", env: { PR_NUMBER: "${{ github.event.pull_request.number }}" }, run: PR_UNLOCK_COMMAND },
391
+ { name: "Release the pull request's locks", env: { PR_NUMBER: "${{ github.event.pull_request.number }}" }, run: prUnlockCommand(lockBin) },
390
392
  ],
391
393
  },
392
394
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "16478bfa3a3a6b6a573c54713b9d6edcef575a934df2b6a2daba331607d0d132",
4
+ "manifest.json": "eab8cbe3ae719023202bcc5ffdbbbd5ca8dd0d263d22ca405024ff9991cf07b6",
5
5
  "meta.json": "c50e0ef3845c370c3ad2210b8bba455980417c0f818aa6ed46e06d4fb8b1b9f9",
6
6
  "types/index.d.ts": "0ead95536e947ddfb98a5acf0ddb88547913b5a53a342df67711c59266f84b3e",
7
7
  "rules/deprecated-action-version.ts": "a9ba7cc924ff1c7055110416d8989273690415bfb5e8dcc32dc9431b9351f5ca",
@@ -78,5 +78,5 @@
78
78
  "skills/chant-github-patterns.md": "bb3abef289a8fdfcf07d6bb2d7289dcb2f38bc0cb0321ea320b78b45a6f548c0",
79
79
  "skills/chant-github-security.md": "aab111cb0871cad30281ce48d7da23663689619351029219e2be019a1a61e394"
80
80
  },
81
- "composite": "9eb8c4e72039a3e7c014959fe55b6846823a49f4097e41c3d4b2b9baebe12ad4"
81
+ "composite": "c581b6a6d25abd873cb8aba19d5ccd648c178312acdbdac07a30900b9b685b5a"
82
82
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github",
3
- "version": "0.124.56",
3
+ "version": "0.124.57",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GitHub",
6
6
  "intrinsics": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-github",
3
- "version": "0.124.56",
3
+ "version": "0.124.57",
4
4
  "description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -61,6 +61,6 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@intentius/chant": "^0.124.56"
64
+ "@intentius/chant": "^0.124.57"
65
65
  }
66
66
  }
@@ -6,10 +6,6 @@ on:
6
6
  - main
7
7
  workflow_dispatch: {}
8
8
 
9
- concurrency:
10
- group: migrations
11
- cancel-in-progress: false
12
-
13
9
  permissions:
14
10
  contents: write
15
11
 
@@ -19,6 +15,9 @@ jobs:
19
15
  - self-hosted
20
16
  - dev
21
17
  container: node:22
18
+ timeout-minutes: 180
19
+ env:
20
+ CHANT_LOCK_WAIT: '10500'
22
21
  steps:
23
22
  - name: Check the job runs where the spec set it to run
24
23
  env:
@@ -47,7 +46,9 @@ jobs:
47
46
  permissions:
48
47
  contents: write
49
48
  id-token: write
49
+ timeout-minutes: 180
50
50
  env:
51
+ CHANT_LOCK_WAIT: '10500'
51
52
  STAGE: apply
52
53
  PLAN_TOKEN_AUDIENCE: plan.example
53
54
  steps:
@@ -95,7 +96,9 @@ jobs:
95
96
  permissions:
96
97
  contents: write
97
98
  id-token: write
99
+ timeout-minutes: 180
98
100
  env:
101
+ CHANT_LOCK_WAIT: '10500'
99
102
  STAGE: apply
100
103
  REGION: us
101
104
  CLOUD_TOKEN_AUDIENCE: us.example
@@ -141,7 +144,9 @@ jobs:
141
144
  permissions:
142
145
  contents: write
143
146
  id-token: write
147
+ timeout-minutes: 180
144
148
  env:
149
+ CHANT_LOCK_WAIT: '10500'
145
150
  STAGE: apply
146
151
  REGION: us
147
152
  CLOUD_TOKEN_AUDIENCE: us.example
@@ -184,7 +189,9 @@ jobs:
184
189
  permissions:
185
190
  contents: write
186
191
  id-token: write
192
+ timeout-minutes: 180
187
193
  env:
194
+ CHANT_LOCK_WAIT: '10500'
188
195
  STAGE: apply
189
196
  PLAN_TOKEN_AUDIENCE: plan.example
190
197
  steps:
@@ -141,8 +141,8 @@ export interface GithubOpPipelineDoc {
141
141
  on: Record<string, unknown>;
142
142
  /** The `env:` mapping, when `options.variables` is set. */
143
143
  env?: Record<string, unknown>;
144
- /** The `concurrency:` mapping — one run at a time per Op. */
145
- concurrency: Record<string, unknown>;
144
+ /** The `concurrency:` mapping — one run at a time per Op. Absent on a waves file (ruling for ci-products 2026-10-28). */
145
+ concurrency?: Record<string, unknown>;
146
146
  /**
147
147
  * The `permissions:` mapping for this Op's finding-mode. GitHub-only:
148
148
  * Forgejo Actions ignores `permissions:` entirely, so the forgejo dialect
@@ -655,14 +655,14 @@ function scopeOpFileToMember(file: GithubOpPipelineFile, member: PipelineMember)
655
655
  return [job, { ...rest, defaults: { run: { "working-directory": member.dir } }, steps }];
656
656
  }),
657
657
  );
658
- const group = (file.doc.concurrency as { group?: unknown }).group;
658
+ const group = (file.doc.concurrency as { group?: unknown } | undefined)?.group;
659
659
  return {
660
660
  name,
661
661
  doc: {
662
662
  ...file.doc,
663
663
  ...(file.doc.name ? { name: `${member.name}/${file.doc.name}` } : {}),
664
664
  on,
665
- concurrency: { ...file.doc.concurrency, ...(typeof group === "string" ? { group: `${member.name}-${group}` } : {}) },
665
+ ...(file.doc.concurrency ? { concurrency: { ...file.doc.concurrency, ...(typeof group === "string" ? { group: `${member.name}-${group}` } : {}) } } : {}),
666
666
  jobsDoc,
667
667
  },
668
668
  };
@@ -105,14 +105,14 @@ describe("a runner per wave and job groups per wave (#3914)", () => {
105
105
  const yaml = files[0]!.yaml;
106
106
  expect(yaml).toMatch(/wave-1-dev:\n\s+runs-on:\n\s+- self-hosted\n\s+- dev\n/);
107
107
  expect(yaml).toMatch(
108
- /wave-2-prod-decide:\n\s+runs-on: plan\n[\s\S]*?permissions:\n\s+contents: write\n\s+id-token: write\n\s+env:\n\s+STAGE: apply\n\s+PLAN_TOKEN_AUDIENCE: plan.example\n/,
108
+ /wave-2-prod-decide:\n\s+runs-on: plan\n[\s\S]*?permissions:\n\s+contents: write\n\s+id-token: write\n\s+timeout-minutes: 180\n\s+env:\n\s+CHANT_LOCK_WAIT: '10500'\n\s+STAGE: apply\n\s+PLAN_TOKEN_AUDIENCE: plan.example\n/,
109
109
  );
110
110
  expect(yaml).toMatch(
111
- /wave-2-prod-us-share-1:\n\s+runs-on:\n\s+group: prod-apply\n\s+labels:\n\s+- linux\n\s+needs:\n\s+- wave-2-prod-decide\n\s+container: node:22\n\s+environment:\n\s+name: prod-us\n\s+permissions:\n\s+contents: write\n\s+id-token: write\n\s+env:\n\s+STAGE: apply\n\s+REGION: us\n\s+CLOUD_TOKEN_AUDIENCE: us.example\n/,
111
+ /wave-2-prod-us-share-1:\n\s+runs-on:\n\s+group: prod-apply\n\s+labels:\n\s+- linux\n\s+needs:\n\s+- wave-2-prod-decide\n\s+container: node:22\n\s+environment:\n\s+name: prod-us\n\s+permissions:\n\s+contents: write\n\s+id-token: write\n\s+timeout-minutes: 180\n\s+env:\n\s+CHANT_LOCK_WAIT: '10500'\n\s+STAGE: apply\n\s+REGION: us\n\s+CLOUD_TOKEN_AUDIENCE: us.example\n/,
112
112
  );
113
113
  expect(yaml).toContain("- run: echo us\n - name: Apply share 1 of job group us of wave 2 (prod)\n run: chant run wave --spec waves.json --wave 2 --group us --share 1\n");
114
114
  // A group with no id tokens of its own gets the wave's.
115
- expect(yaml).toMatch(/wave-2-prod-eu:\n\s+runs-on: eu-apply\n[\s\S]*?name: prod-eu\n\s+permissions:\n\s+contents: write\n\s+id-token: write\n\s+env:\n\s+STAGE: apply\n\s+PLAN_TOKEN_AUDIENCE: plan.example\n/);
115
+ expect(yaml).toMatch(/wave-2-prod-eu:\n\s+runs-on: eu-apply\n[\s\S]*?name: prod-eu\n\s+permissions:\n\s+contents: write\n\s+id-token: write\n\s+timeout-minutes: 180\n\s+env:\n\s+CHANT_LOCK_WAIT: '10500'\n\s+STAGE: apply\n\s+PLAN_TOKEN_AUDIENCE: plan.example\n/);
116
116
  expect(yaml).toContain("run: chant run wave --spec waves.json --wave 2 --group eu\n");
117
117
  });
118
118
 
@@ -139,3 +139,19 @@ describe("a runner per wave and job groups per wave (#3914)", () => {
139
139
  expect(jobs.every((j) => j.command[0] === "chant")).toBe(true);
140
140
  });
141
141
  });
142
+
143
+ describe("the ledger lock (ruling for ci-products 2026-10-28)", () => {
144
+ test("the waves workflow carries no concurrency group; each wave job carries its timeout and the lock wait under it", () => {
145
+ const yaml = generateGithubOpWavesPipeline(spec, { specFile: "waves.json" }).files[0]!.yaml;
146
+ expect(yaml).not.toMatch(/^concurrency:/m);
147
+ expect(yaml).not.toContain("group: migrations");
148
+ expect(yaml.match(/timeout-minutes: 180\n/g)).toHaveLength(4);
149
+ expect(yaml.match(/CHANT_LOCK_WAIT: '10500'\n/g)).toHaveLength(4);
150
+ });
151
+
152
+ test("jobTimeoutMinutes sets both", () => {
153
+ const yaml = generateGithubOpWavesPipeline(spec, { specFile: "waves.json", jobTimeoutMinutes: 60 }).files[0]!.yaml;
154
+ expect(yaml).toContain("timeout-minutes: 60\n");
155
+ expect(yaml).toContain("CHANT_LOCK_WAIT: '3300'\n");
156
+ });
157
+ });
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Op waves → GitHub Actions workflow (#3679).
3
3
  *
4
- * One workflow for the whole spec, on a push to its branches (and by hand),
5
- * one run at a time. Each job is one of core's `opWaveJobs`: a wave, or a wide
4
+ * One workflow for the whole spec, on a push to its branches (and by hand).
5
+ * Each job is one of core's `opWaveJobs`: a wave, or a wide
6
6
  * wave's deciding job and share jobs, chained by `needs:`. Re-running the
7
7
  * failed jobs after `chant approve` carries on from that wave.
8
8
  *
@@ -79,6 +79,7 @@ import {
79
79
  notifyVariables,
80
80
  opWaveGroupOf,
81
81
  opWaveJobs,
82
+ opWaveJobTimeout,
82
83
  opWaveOutcomeDir,
83
84
  opWavesControlTarget,
84
85
  opWavesLastJobs,
@@ -146,6 +147,8 @@ export function buildGithubOpWavesDoc(
146
147
  }
147
148
  }
148
149
  const permissions = { contents: "write", ...(byReview ? { "pull-requests": "read" } : {}) };
150
+ // The ruling for ci-products (2026-10-28): every wave job carries its timeout and the lock wait under it.
151
+ const timeout = opWaveJobTimeout(options.jobTimeoutMinutes);
149
152
 
150
153
  const jobsDoc: Record<string, unknown> = {};
151
154
  for (const job of jobs) {
@@ -194,7 +197,8 @@ export function buildGithubOpWavesDoc(
194
197
  ...(environment ? { environment: environmentDoc(environment) } : {}),
195
198
  // #3914: a job's own permissions replace the workflow's, so they repeat them.
196
199
  ...(idToken ? { permissions: { ...permissions, "id-token": "write" } } : {}),
197
- ...nonEmpty("env", group || idTokens ? { ...wave.variables, ...group?.variables, ...idTokenAudienceEnv(idTokens) } : wave.variables),
200
+ "timeout-minutes": timeout.minutes,
201
+ env: { ...timeout.env, ...(group || idTokens ? { ...wave.variables, ...group?.variables, ...idTokenAudienceEnv(idTokens) } : wave.variables) },
198
202
  steps,
199
203
  };
200
204
  }
@@ -211,7 +215,8 @@ export function buildGithubOpWavesDoc(
211
215
  name: spec.name,
212
216
  on: { push: { branches: pushBranches(spec.branches) }, workflow_dispatch: {} },
213
217
  ...nonEmpty("env", env),
214
- concurrency: concurrency(spec.name),
218
+ // The ruling for ci-products (2026-10-28): no workflow-level concurrency group. Runs on the target and repo grains wait for
219
+ // their rows on chant's ledger, oldest commit first; plunge stacks on the resource grain lease their resource rows and run.
215
220
  permissions,
216
221
  jobsDoc,
217
222
  };
@@ -78,7 +78,7 @@ import {
78
78
  PR_PLAN_NOTE_JOB,
79
79
  PR_LOCK_JOB,
80
80
  PR_UNLOCK_JOB,
81
- PR_UNLOCK_COMMAND,
81
+ prUnlockCommand,
82
82
  prLockCommand,
83
83
  prCommentLockJobs,
84
84
  prCommentPreviewJob,
@@ -453,6 +453,8 @@ function prLoopGithubDoc(
453
453
  const prEnv = loop.prEnvironment ? prEnvGithubJobs(loop.forge ?? "github", image, beforeScript, extraScript, loop, member) : undefined;
454
454
  // #4327: with ci.locks: plan the pull_request trigger fires on close too, for the unlock job, and the plan skips that event.
455
455
  const locks = loop.locks === "plan";
456
+ // plywood#22: the lock lines run through the product's bin, as its other job lines do.
457
+ const lockBin = resolveProductWords(loop.words).bin;
456
458
  const closes = Boolean(prEnv) || locks;
457
459
  const lockJobs: Record<string, unknown> = locks
458
460
  ? {
@@ -471,7 +473,7 @@ function prLoopGithubDoc(
471
473
  {
472
474
  name: "Lock the components the plan selected",
473
475
  env: { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.event.pull_request.head.sha }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN },
474
- run: prLockCommand(forgeName),
476
+ run: prLockCommand(forgeName, lockBin),
475
477
  },
476
478
  ],
477
479
  },
@@ -483,7 +485,7 @@ function prLoopGithubDoc(
483
485
  steps: [
484
486
  { name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
485
487
  ...runSteps(beforeScript),
486
- { name: "Release the pull request's locks", env: { PR_NUMBER: "${{ github.event.pull_request.number }}" }, run: PR_UNLOCK_COMMAND },
488
+ { name: "Release the pull request's locks", env: { PR_NUMBER: "${{ github.event.pull_request.number }}" }, run: prUnlockCommand(lockBin) },
487
489
  ],
488
490
  },
489
491
  }
@@ -56,6 +56,16 @@ describe("the GitHub workflow with ci.locks: plan", () => {
56
56
  });
57
57
  });
58
58
 
59
+ describe("a product's words (plywood#22)", () => {
60
+ test("the lock and unlock lines run through the product's bin, so its job image needs no chant on PATH", () => {
61
+ const doc = gen({ locks: "plan", words: { product: "plywood", bin: "npx --no-install plywood" } });
62
+ const lock = doc.jobs.lock!.steps.find((s) => s.run?.includes(" lock --from-report"))!;
63
+ expect(lock.run).toBe('npx --no-install plywood lock --from-report .chant/pr/pr-plan.json --pr "$PR_NUMBER" --head "$HEAD_SHA" --stage plan --forge github');
64
+ const unlock = doc.jobs.unlock!.steps.find((s) => s.run?.includes(" unlock --pr"))!;
65
+ expect(unlock.run).toBe('npx --no-install plywood unlock --pr "$PR_NUMBER"');
66
+ });
67
+ });
68
+
59
69
  describe("with a pull request environment too", () => {
60
70
  test("the pull_request types and the plan condition are the same as with the environment alone", () => {
61
71
  const both = gen({ locks: "plan", prEnvironment: true });