@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.
- package/dist/components/generate-op-pipeline.d.ts +2 -2
- package/dist/components/generate-op-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-pipeline.js +2 -2
- package/dist/components/generate-op-waves-pipeline.d.ts +2 -2
- package/dist/components/generate-op-waves-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-waves-pipeline.js +9 -5
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +5 -3
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/__golden__/op-waves-jobs.yml +11 -4
- package/src/components/generate-op-pipeline.ts +4 -4
- package/src/components/generate-op-waves-pipeline.test.ts +19 -3
- package/src/components/generate-op-waves-pipeline.ts +9 -4
- package/src/components/generate-pipeline.ts +5 -3
- package/src/components/generate-pr-pipeline.locks.test.ts +10 -0
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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,
|
|
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:
|
|
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
|
}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
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": "
|
|
81
|
+
"composite": "c581b6a6d25abd873cb8aba19d5ccd648c178312acdbdac07a30900b9b685b5a"
|
|
82
82
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.124.
|
|
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.
|
|
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
|
|
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 })
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 });
|