@intentius/chant 0.124.40 → 0.124.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__fixtures__/oidc-emulator.d.ts +49 -0
- package/dist/__fixtures__/oidc-emulator.d.ts.map +1 -0
- package/dist/__fixtures__/oidc-emulator.js +89 -0
- package/dist/ci-handover.d.ts +117 -0
- package/dist/ci-handover.d.ts.map +1 -0
- package/dist/ci-handover.js +217 -0
- package/dist/ci-identity.d.ts +190 -0
- package/dist/ci-identity.d.ts.map +1 -0
- package/dist/ci-identity.js +219 -0
- package/dist/cli/handlers/approve.d.ts +2 -1
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +20 -6
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +15 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/gate-resume.d.ts +3 -0
- package/dist/cli/handlers/gate-resume.d.ts.map +1 -1
- package/dist/cli/handlers/gate-resume.js +15 -2
- package/dist/cli/handlers/lock.d.ts +8 -0
- package/dist/cli/handlers/lock.d.ts.map +1 -0
- package/dist/cli/handlers/lock.js +197 -0
- package/dist/cli/handlers/pr-env.d.ts +34 -0
- package/dist/cli/handlers/pr-env.d.ts.map +1 -0
- package/dist/cli/handlers/pr-env.js +436 -0
- package/dist/cli/handlers/pr.d.ts +74 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +503 -53
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +9 -0
- package/dist/cli/handlers/query.d.ts +6 -0
- package/dist/cli/handlers/query.d.ts.map +1 -0
- package/dist/cli/handlers/query.js +80 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +41 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +31 -2
- package/dist/cli/registry.d.ts +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/fan-out-run.d.ts +28 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +27 -3
- package/dist/components/pr-environment.d.ts +298 -0
- package/dist/components/pr-environment.d.ts.map +1 -0
- package/dist/components/pr-environment.js +406 -0
- package/dist/components/pr-hooks.d.ts +66 -0
- package/dist/components/pr-hooks.d.ts.map +1 -0
- package/dist/components/pr-hooks.js +119 -0
- package/dist/components/pr-pipeline.d.ts +72 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +96 -5
- package/dist/components/pr-policy.d.ts +109 -0
- package/dist/components/pr-policy.d.ts.map +1 -0
- package/dist/components/pr-policy.js +150 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +5 -3
- package/dist/gate-decision.d.ts +15 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +17 -2
- package/dist/gate-policy.d.ts +140 -0
- package/dist/gate-policy.d.ts.map +1 -0
- package/dist/gate-policy.js +242 -0
- package/dist/lifecycle/gate-checkpoint.d.ts +1 -1
- package/dist/lifecycle/gate-checkpoint.js +1 -1
- package/dist/{workspace/trust → lifecycle}/gate-seal.d.ts +1 -1
- package/dist/lifecycle/gate-seal.d.ts.map +1 -0
- package/dist/lifecycle/query.d.ts +68 -0
- package/dist/lifecycle/query.d.ts.map +1 -0
- package/dist/lifecycle/query.js +246 -0
- package/dist/lifecycle/run-ledger.d.ts +13 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +16 -0
- package/dist/lifecycle/target-lock.d.ts +97 -0
- package/dist/lifecycle/target-lock.d.ts.map +1 -0
- package/dist/lifecycle/target-lock.js +286 -0
- package/dist/op/activities/activity-contracts.d.ts +30 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +26 -0
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +4 -0
- package/dist/op/activities/review-agent.d.ts +137 -0
- package/dist/op/activities/review-agent.d.ts.map +1 -0
- package/dist/op/activities/review-agent.js +266 -0
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +11 -0
- package/dist/op/cron.d.ts +8 -0
- package/dist/op/cron.d.ts.map +1 -1
- package/dist/op/cron.js +51 -0
- package/dist/op/gate-approval.d.ts +20 -13
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -18
- package/dist/op/gate-quorum.d.ts +131 -0
- package/dist/op/gate-quorum.d.ts.map +1 -0
- package/dist/op/gate-quorum.js +183 -0
- package/dist/op/gate-resume.d.ts +4 -2
- package/dist/op/gate-resume.d.ts.map +1 -1
- package/dist/op/gate-resume.js +5 -4
- package/dist/op/gate-review.js +1 -1
- package/dist/op/gate.d.ts +32 -4
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +40 -24
- package/dist/op/index.d.ts +6 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +4 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +9 -3
- package/dist/op/op-waves-run.d.ts +62 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +190 -11
- package/dist/op/op-waves.d.ts +55 -3
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +63 -8
- package/dist/op/schedule-overdue.d.ts +57 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -0
- package/dist/op/schedule-overdue.js +138 -0
- package/dist/override-gate.d.ts +2 -2
- package/dist/override-gate.d.ts.map +1 -1
- package/dist/override-gate.js +1 -1
- package/dist/pr-forge.d.ts +36 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +82 -0
- package/dist/pr-loop.d.ts +90 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +77 -3
- package/dist/pr-post.d.ts +72 -0
- package/dist/pr-post.d.ts.map +1 -0
- package/dist/pr-post.js +134 -0
- package/dist/wave-blocked.d.ts +79 -0
- package/dist/wave-blocked.d.ts.map +1 -0
- package/dist/wave-blocked.js +245 -0
- package/dist/wave-engine.d.ts +6 -0
- package/dist/wave-engine.d.ts.map +1 -1
- package/dist/wave-engine.js +2 -0
- package/dist/wave-runtime.mjs +242 -242
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/two-member-workspace.js +1 -1
- package/dist/workspace/ci-branches.d.ts +51 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -0
- package/dist/workspace/ci-branches.js +69 -0
- package/dist/workspace/ci-cloud-identity.d.ts +11 -0
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -0
- package/dist/workspace/ci-cloud-identity.js +24 -0
- package/dist/workspace/ci-locks.d.ts +12 -0
- package/dist/workspace/ci-locks.d.ts.map +1 -0
- package/dist/workspace/ci-locks.js +6 -0
- package/dist/workspace/ci-run.d.ts +17 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +64 -3
- package/dist/workspace/declaration.d.ts +63 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +72 -1
- package/dist/workspace/declaration.schema.json +147 -0
- package/dist/workspace/identity-gate-rule.d.ts +1 -1
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +1 -1
- package/dist/workspace/identity.d.ts +1 -1
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +8 -5
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +23 -0
- package/dist/workspace/pr-report.schema.json +73 -2
- package/dist/workspace/security.d.ts.map +1 -1
- package/dist/workspace/security.js +4 -0
- package/dist/workspace/status-gates.d.ts +3 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +8 -7
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +4 -1
- package/dist/workspace/trust/seal.d.ts +2 -2
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +2 -2
- package/package.json +1 -1
- package/src/__fixtures__/oidc-emulator.ts +118 -0
- package/src/ci-handover.test.ts +209 -0
- package/src/ci-handover.ts +237 -0
- package/src/ci-identity.test.ts +201 -0
- package/src/ci-identity.ts +310 -0
- package/src/cli/handlers/approve.ts +18 -5
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/fan-out.ts +17 -4
- package/src/cli/handlers/gate-resume.ts +16 -2
- package/src/cli/handlers/lock.test.ts +98 -0
- package/src/cli/handlers/lock.ts +202 -0
- package/src/cli/handlers/operator.test.ts +3 -1
- package/src/cli/handlers/pr-env.ts +462 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +247 -0
- package/src/cli/handlers/pr.test.ts +184 -0
- package/src/cli/handlers/pr.ts +496 -59
- package/src/cli/handlers/promote.ts +9 -0
- package/src/cli/handlers/query.ts +78 -0
- package/src/cli/main.ts +41 -2
- package/src/cli/parse-args.ts +23 -2
- package/src/cli/registry.ts +12 -2
- package/src/components/fan-out-run.ts +39 -2
- package/src/components/fan-out-waves.test.ts +45 -1
- package/src/components/pr-environment.test.ts +249 -0
- package/src/components/pr-environment.ts +538 -0
- package/src/components/pr-hooks.test.ts +118 -0
- package/src/components/pr-hooks.ts +157 -0
- package/src/components/pr-pipeline.ts +130 -5
- package/src/components/pr-policy.test.ts +145 -0
- package/src/components/pr-policy.ts +211 -0
- package/src/components/pr-run.ts +5 -2
- package/src/gate-decision-bundle.test.ts +4 -3
- package/src/gate-decision.ts +39 -2
- package/src/gate-policy-bundle.test.ts +28 -0
- package/src/gate-policy.test.ts +162 -0
- package/src/gate-policy.ts +298 -0
- package/src/lifecycle/gate-checkpoint.ts +1 -1
- package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
- package/src/lifecycle/query.test.ts +91 -0
- package/src/lifecycle/query.ts +315 -0
- package/src/lifecycle/run-ledger.ts +18 -0
- package/src/lifecycle/target-lock.test.ts +195 -0
- package/src/lifecycle/target-lock.ts +347 -0
- package/src/op/activities/activity-contracts.ts +31 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activities/review-agent.test.ts +240 -0
- package/src/op/activities/review-agent.ts +355 -0
- package/src/op/builders.ts +13 -0
- package/src/op/cron.ts +44 -0
- package/src/op/gate-alias.test.ts +2 -2
- package/src/op/gate-approval.test.ts +30 -9
- package/src/op/gate-approval.ts +23 -26
- package/src/op/gate-quorum.test.ts +139 -0
- package/src/op/gate-quorum.ts +248 -0
- package/src/op/gate-resume.ts +6 -3
- package/src/op/gate-review.ts +1 -1
- package/src/op/gate.ts +64 -23
- package/src/op/index.ts +13 -1
- package/src/op/local-executor.ts +9 -2
- package/src/op/op-waves-hook-targets.test.ts +126 -0
- package/src/op/op-waves-policy-hold.test.ts +66 -0
- package/src/op/op-waves-quorum.test.ts +101 -0
- package/src/op/op-waves-run.ts +233 -11
- package/src/op/op-waves.ts +83 -10
- package/src/op/schedule-overdue.test.ts +220 -0
- package/src/op/schedule-overdue.ts +161 -0
- package/src/override-gate-bundle.test.ts +1 -1
- package/src/override-gate.ts +2 -2
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +33 -0
- package/src/pr-forge.ts +114 -0
- package/src/pr-loop-bundle.test.ts +2 -2
- package/src/pr-loop.test.ts +58 -0
- package/src/pr-loop.ts +146 -4
- package/src/pr-post.test.ts +141 -0
- package/src/pr-post.ts +159 -0
- package/src/wave-blocked-bundle.test.ts +25 -0
- package/src/wave-blocked.test.ts +146 -0
- package/src/wave-blocked.ts +271 -0
- package/src/wave-engine.ts +8 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +2 -2
- package/src/workspace/ci-branches.test.ts +62 -0
- package/src/workspace/ci-branches.ts +79 -0
- package/src/workspace/ci-cloud-identity.test.ts +53 -0
- package/src/workspace/ci-cloud-identity.ts +23 -0
- package/src/workspace/ci-locks.ts +16 -0
- package/src/workspace/ci-run-index.test.ts +2 -1
- package/src/workspace/ci-run.test.ts +107 -8
- package/src/workspace/ci-run.ts +72 -4
- package/src/workspace/declaration.schema.json +147 -0
- package/src/workspace/declaration.test.ts +76 -0
- package/src/workspace/declaration.ts +134 -2
- package/src/workspace/identity-gate-rule.ts +1 -1
- package/src/workspace/identity.ts +1 -1
- package/src/workspace/lineage-upgrade.ts +11 -8
- package/src/workspace/member-pipeline.test.ts +29 -0
- package/src/workspace/member-pipeline.ts +30 -0
- package/src/workspace/pr-report.schema.json +73 -2
- package/src/workspace/security.ts +4 -0
- package/src/workspace/status-gates.ts +9 -6
- package/src/workspace/status.ts +4 -1
- package/src/workspace/trust/gate-seal.test.ts +1 -1
- package/src/workspace/trust/seal.ts +2 -2
- package/dist/workspace/trust/gate-seal.d.ts.map +0 -1
- /package/dist/{workspace/trust → lifecycle}/gate-seal.js +0 -0
|
@@ -26,6 +26,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
26
26
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { loadChantConfig, type ChantConfig } from "../../config";
|
|
28
28
|
import { promotionOrder } from "../../workspace/promotion-order";
|
|
29
|
+
import { releaseTargetRefusal } from "../../components/pr-environment";
|
|
29
30
|
import { resolvePromotionSource } from "../../components/promote-order";
|
|
30
31
|
import { fetchLifecycle, getHeadCommit, pushLifecycle } from "../../lifecycle/git";
|
|
31
32
|
import {
|
|
@@ -897,6 +898,14 @@ export async function runComponentsPromote(ctx: CommandContext): Promise<number>
|
|
|
897
898
|
console.error(formatError({ message: "--to <env> is required", hint: PROMOTE_USAGE }));
|
|
898
899
|
return 1;
|
|
899
900
|
}
|
|
901
|
+
// #4298: a pull request's environment is neither a release's target nor its source.
|
|
902
|
+
for (const env of [to, args.migrateFrom]) {
|
|
903
|
+
const refusal = env ? releaseTargetRefusal(env) : undefined;
|
|
904
|
+
if (refusal) {
|
|
905
|
+
console.error(formatError({ message: refusal }));
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
900
909
|
const who = actorOrRefuse(ctx);
|
|
901
910
|
if (typeof who === "number") return who;
|
|
902
911
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant query "<sql>" [dir] [--member <name>] [--json]` (#4297): one
|
|
3
|
+
* read-only SQL statement over the snapshots, Op runs, gates, deploys and
|
|
4
|
+
* releases on the local `chant/lifecycle` branch. The tables and the engine
|
|
5
|
+
* live in `../../lifecycle/query.ts`; this reads the ledgers and prints.
|
|
6
|
+
*
|
|
7
|
+
* Inside a workspace every member's ledger is read, each row naming its
|
|
8
|
+
* member; `--member` keeps the members it names. Outside one the project's
|
|
9
|
+
* own ledger is read and `member` is NULL. It reads the local branch and
|
|
10
|
+
* never fetches.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { formatError, formatWarning } from "../format";
|
|
15
|
+
import type { CommandContext } from "../registry";
|
|
16
|
+
import type { QueryLedger } from "../../lifecycle/query";
|
|
17
|
+
import { findWorkspaceRoot } from "../../project-root";
|
|
18
|
+
|
|
19
|
+
const USAGE = 'chant query "<sql>" [dir] [--member <name>] [--json]';
|
|
20
|
+
|
|
21
|
+
/** The ledgers `chant query` reads from `cwd`: every member's in a workspace, else the project's own. */
|
|
22
|
+
export async function queryLedgers(cwd: string, members?: readonly string[]): Promise<QueryLedger[]> {
|
|
23
|
+
const { gitLedgerReadPort } = await import("../../lifecycle/git");
|
|
24
|
+
const found = findWorkspaceRoot(cwd);
|
|
25
|
+
if (!found) return [{ member: null, port: gitLedgerReadPort({ cwd }) }];
|
|
26
|
+
// Workspace code loads only when there is a workspace, so a level-0 project never pays for it.
|
|
27
|
+
const { readDeclaration, WorkspaceReadError } = await import("../../workspace/declaration");
|
|
28
|
+
const { workingTree } = await import("../../workspace/tree");
|
|
29
|
+
const { QueryError } = await import("../../lifecycle/query");
|
|
30
|
+
let declaration;
|
|
31
|
+
try {
|
|
32
|
+
declaration = readDeclaration(workingTree(found.dir), "");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err instanceof WorkspaceReadError) throw new QueryError(`${err.code}: ${err.message}`);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
const wanted = members && members.length > 0 ? new Set(members) : null;
|
|
38
|
+
if (wanted) {
|
|
39
|
+
const unknown = [...wanted].filter((n) => !declaration.members.some((m) => m.name === n));
|
|
40
|
+
if (unknown.length > 0) throw new QueryError(`--member ${unknown.join(", ")} names no member of the workspace`);
|
|
41
|
+
}
|
|
42
|
+
return declaration.members
|
|
43
|
+
.filter((m) => !wanted || wanted.has(m.name))
|
|
44
|
+
.map((m) => ({ member: m.name, port: gitLedgerReadPort({ cwd: found.dir, member: m.dir === "." ? "." : m.name }) }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function runQueryCommand(ctx: CommandContext): Promise<number> {
|
|
48
|
+
const { args } = ctx;
|
|
49
|
+
const sql = args.positionals?.[0];
|
|
50
|
+
if (sql === undefined || sql.trim() === "") {
|
|
51
|
+
console.error(formatError({ message: "chant query needs a statement", hint: `${USAGE}, such as chant query "SELECT env, count(*) FROM resources GROUP BY env"` }));
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
const cwd = resolve(args.positionals?.[1] ?? ".");
|
|
55
|
+
if (!existsSync(cwd)) {
|
|
56
|
+
console.error(formatError({ message: `${cwd} does not exist`, hint: USAGE }));
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const { loadQueryData, runQuery, formatQueryResult, QueryError } = await import("../../lifecycle/query");
|
|
60
|
+
try {
|
|
61
|
+
const load = await loadQueryData(await queryLedgers(cwd, args.members));
|
|
62
|
+
const result = await runQuery(sql, load);
|
|
63
|
+
if (args.json) {
|
|
64
|
+
console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(formatQueryResult(result));
|
|
67
|
+
const bad = Object.entries(result.malformed).filter(([, n]) => n > 0);
|
|
68
|
+
if (bad.length > 0) console.error(formatWarning({ message: `left out what could not be read: ${bad.map(([t, n]) => `${n} in ${t}`).join(", ")}` }));
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof QueryError) {
|
|
73
|
+
console.error(formatError({ message: err.message, hint: USAGE }));
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/cli/main.ts
CHANGED
|
@@ -173,6 +173,11 @@ Commands:
|
|
|
173
173
|
terms; --live or --at <ref> with --env <name> reads the
|
|
174
174
|
estate or a recorded snapshot; --show <attr>,
|
|
175
175
|
--check-live, --check-snapshot, --fail-on-drift)
|
|
176
|
+
query "<sql>" [dir] Run one read-only SQL statement over chant/lifecycle:
|
|
177
|
+
the resources and snapshots each lexicon observed, Op
|
|
178
|
+
runs, gates, deploys, releases and cuts, loaded into
|
|
179
|
+
node:sqlite (schema v1, listed in chant_schema).
|
|
180
|
+
--member <name> keeps members; --json prints rows
|
|
176
181
|
generate Generate typed code from sources chant.config declares
|
|
177
182
|
(k8s.crds, helm.charts) into src/generated/<lexicon>/
|
|
178
183
|
(codegen.outDir); --check exits 1 when the committed
|
|
@@ -558,6 +563,15 @@ Ops:
|
|
|
558
563
|
applies it once, if .chant/overriders at base lists
|
|
559
564
|
--actor
|
|
560
565
|
|
|
566
|
+
lock <target>... Lock targets for a pull request on chant/lifecycle
|
|
567
|
+
(--pr <n>, --stage plan|apply|lock, --head <sha>,
|
|
568
|
+
--by <who>, --gate <name>, --json). --from-report
|
|
569
|
+
<pr-plan.json> locks the components a plan selected.
|
|
570
|
+
--forge <kind> reads which holders are still open and
|
|
571
|
+
posts chant/lock on --head. Exits 3 when another open
|
|
572
|
+
pull request holds a target. --list prints the locks
|
|
573
|
+
unlock [<target>...] Release the locks a pull request holds (--pr <n>,
|
|
574
|
+
--gate <name>, --json)
|
|
561
575
|
graph Show Op dependency graph (--stacks for cross-stack order,
|
|
562
576
|
--format ir|mermaid|dot|layout for the lint-gated graph IR,
|
|
563
577
|
a Mermaid flowchart, Graphviz DOT, or node positions;
|
|
@@ -1003,11 +1017,26 @@ Component release ledger + status:
|
|
|
1003
1017
|
report and note (--base <ref> --pr <n>
|
|
1004
1018
|
[--forge github|gitlab|forgejo] posts them);
|
|
1005
1019
|
applies nothing
|
|
1020
|
+
components pr-post Post the plan note and the chant/plan status
|
|
1021
|
+
from the report pr-plan --no-status wrote
|
|
1022
|
+
(--pr <n> --head <sha> --env <env> --forge
|
|
1023
|
+
<kind>), in a job that runs none of the
|
|
1024
|
+
change's code (#4295)
|
|
1006
1025
|
components pr-record On merge (#4168): plan the same members again,
|
|
1007
1026
|
store the change set and write the merge record
|
|
1008
1027
|
_merges/<commit>.json; applies and refuses
|
|
1009
1028
|
nothing, and a plan that differs from the
|
|
1010
1029
|
approved one sets a failing chant/record status
|
|
1030
|
+
components pr-env An environment per open pull request (#4298): up
|
|
1031
|
+
--base <ref> --pr <n> applies the change to pr-<n>,
|
|
1032
|
+
derived from ci.prEnvironment.template, once an
|
|
1033
|
+
approval of its plan stands (chant approve pr-<n>
|
|
1034
|
+
pr-env --plan <digest>); post --pr <n> --head
|
|
1035
|
+
<sha> keeps the record up --no-push handed on and
|
|
1036
|
+
posts the note; down --pr <n> tears down
|
|
1037
|
+
only what carries pr-<n>'s ownership marker; sweep
|
|
1038
|
+
tears down each one whose TTL passed or whose pull
|
|
1039
|
+
request closed. Never a release target
|
|
1011
1040
|
components pr-apply With --apply-on-merge: plan the same members again and apply
|
|
1012
1041
|
them only if an approval stands for that digest
|
|
1013
1042
|
(chant approve pr-<n> pr-apply --plan <digest>);
|
|
@@ -1201,7 +1230,8 @@ Options:
|
|
|
1201
1230
|
chant.config.ts's build.sandbox: true; #1045)
|
|
1202
1231
|
--param <name=value> (build, graph, run --components, components fan-out,
|
|
1203
1232
|
components promote, components rollback,
|
|
1204
|
-
components redeploy,
|
|
1233
|
+
components redeploy, components pr-env up,
|
|
1234
|
+
release cut) Bind a
|
|
1205
1235
|
declared build-time parameter (chant.config.ts's buildParams)
|
|
1206
1236
|
to a value, for source to read as params.<name>
|
|
1207
1237
|
(#1064) instead of process.env — repeatable.
|
|
@@ -1213,7 +1243,8 @@ Options:
|
|
|
1213
1243
|
chant.template.json declares instead (#2627).
|
|
1214
1244
|
--params-file <path> (build, graph, run --components, components fan-out,
|
|
1215
1245
|
components promote, components rollback,
|
|
1216
|
-
components redeploy,
|
|
1246
|
+
components redeploy, components pr-env up,
|
|
1247
|
+
release cut) JSON file
|
|
1217
1248
|
of { "name": value } build-time parameter values
|
|
1218
1249
|
(#1064). Second precedence, after --param.
|
|
1219
1250
|
init --from refuses it: pass --param instead.
|
|
@@ -1441,6 +1472,8 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1441
1472
|
{ name: "describe", handler: runDescribe },
|
|
1442
1473
|
{ name: "explain", handler: runExplain },
|
|
1443
1474
|
{ name: "search", handler: runSearch },
|
|
1475
|
+
// #4297 — SQL over the snapshots, runs, gates, deploys and releases on chant/lifecycle. Imported on first use.
|
|
1476
|
+
{ name: "query", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/query")).runQueryCommand(ctx) },
|
|
1444
1477
|
// chant#2591 — `import --agents` and `audit` read chant.config statically
|
|
1445
1478
|
// and never evaluate it (chant#2589 for audit, which audits code it must not run).
|
|
1446
1479
|
{ name: "import", handler: runImport, runsNoConfig: (args) => args.agents === true },
|
|
@@ -1524,6 +1557,9 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1524
1557
|
{ name: "operator log", handler: runOperatorLog },
|
|
1525
1558
|
{ name: "operator", handler: runOperator },
|
|
1526
1559
|
{ name: "approve", handler: runApprove },
|
|
1560
|
+
// #4293 — target locks a pull request holds on chant/lifecycle. Imported on first use.
|
|
1561
|
+
{ name: "lock", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/lock")).runLock(ctx) },
|
|
1562
|
+
{ name: "unlock", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/lock")).runUnlock(ctx) },
|
|
1527
1563
|
|
|
1528
1564
|
{ name: "graph", handler: runGraph },
|
|
1529
1565
|
{ name: "vendor", handler: runVendor },
|
|
@@ -1613,7 +1649,10 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1613
1649
|
// #3183 — the pull-request loop the generated CI runs. Imported on first use.
|
|
1614
1650
|
{ name: "components pr-plan", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrPlan(ctx) },
|
|
1615
1651
|
{ name: "components pr-apply", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrApply(ctx) },
|
|
1652
|
+
// #4295: posts from the plan report in a job that runs none of the change's code, so it loads no plugins.
|
|
1653
|
+
{ name: "components pr-post", handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrPost(ctx) },
|
|
1616
1654
|
{ name: "components pr-record", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrRecord(ctx) },
|
|
1655
|
+
{ name: "components pr-env", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr-env")).runComponentsPrEnv(ctx) },
|
|
1617
1656
|
{ name: "components status", requiresPlugins: true, handler: runComponentsStatus },
|
|
1618
1657
|
{ name: "components release", handler: runComponentsReleaseRecord },
|
|
1619
1658
|
{ name: "components export", handler: runComponentsExport },
|
package/src/cli/parse-args.ts
CHANGED
|
@@ -43,6 +43,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
43
43
|
"--stacks",
|
|
44
44
|
"--components",
|
|
45
45
|
"--pr-build",
|
|
46
|
+
"--pr-env",
|
|
46
47
|
"--up",
|
|
47
48
|
"--down",
|
|
48
49
|
"--include-dependents",
|
|
@@ -72,6 +73,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
72
73
|
"--in-place",
|
|
73
74
|
"--require-runner-start",
|
|
74
75
|
"--no-push",
|
|
76
|
+
"--list",
|
|
75
77
|
"--no-require-review",
|
|
76
78
|
"--expire",
|
|
77
79
|
"--check-live",
|
|
@@ -396,11 +398,26 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
396
398
|
if (!result.forge || result.forge.startsWith("-")) throw new Error("--forge needs a forge: --forge github|gitlab|forgejo");
|
|
397
399
|
} else if (arg === "--require-review") {
|
|
398
400
|
result.requireReview = true;
|
|
401
|
+
} else if (arg === "--list") {
|
|
402
|
+
// `chant lock --list` (#4293): print the target locks instead of taking any.
|
|
403
|
+
result.list = true;
|
|
404
|
+
} else if (arg === "--stage") {
|
|
405
|
+
// `chant lock --stage plan|apply|lock` (#4293): how the lock was taken.
|
|
406
|
+
result.lockStage = args[++i];
|
|
407
|
+
if (!result.lockStage || result.lockStage.startsWith("-")) throw new Error("--stage needs plan, apply or lock: --stage <stage>");
|
|
408
|
+
} else if (arg === "--from-report") {
|
|
409
|
+
// `chant lock --from-report <pr-plan.json>` (#4293): lock the components a pull-request report lists.
|
|
410
|
+
result.fromReport = args[++i];
|
|
411
|
+
if (!result.fromReport || result.fromReport.startsWith("-")) throw new Error("--from-report needs a report file: --from-report <pr-plan.json>");
|
|
399
412
|
} else if (arg === "--report-url") {
|
|
400
413
|
// `chant components pr-plan|pr-apply --report-url <url>` (#4007).
|
|
401
414
|
const url = args[++i];
|
|
402
415
|
if (!url || url.startsWith("-")) throw new Error("--report-url needs a URL: --report-url s3://<bucket>/<prefix> or file://<directory>");
|
|
403
416
|
result.reportUrl = url;
|
|
417
|
+
} else if (arg === "--result") {
|
|
418
|
+
// `chant components pr-post --result <result>` (#4295): the plan job's own result, which fails the status when it failed.
|
|
419
|
+
result.planResult = args[++i];
|
|
420
|
+
if (!result.planResult || result.planResult.startsWith("-")) throw new Error("--result needs the plan job's result: --result success|failure|cancelled");
|
|
404
421
|
} else if (arg === "--no-status") {
|
|
405
422
|
// `chant ci run|serve --no-status` (#4097): post no commit status for the job.
|
|
406
423
|
result.noStatus = true;
|
|
@@ -422,6 +439,9 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
422
439
|
} else if (arg === "--apply-on-merge") {
|
|
423
440
|
// `chant build --pr-loop --apply-on-merge` and `chant ci serve --apply-on-merge` (#4168): opt into applying on merge.
|
|
424
441
|
result.applyOnMerge = true;
|
|
442
|
+
} else if (arg === "--pr-env") {
|
|
443
|
+
// `chant build --pr-loop --pr-env` (#4298): add the pull request environment's jobs.
|
|
444
|
+
result.prEnv = true;
|
|
425
445
|
} else if (arg === "--pr-build") {
|
|
426
446
|
// `chant build --pr-loop --pr-build` (#4088), and `chant ci run --pr-build` (#4094).
|
|
427
447
|
result.prBuild = true;
|
|
@@ -864,8 +884,9 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
864
884
|
} else if (arg === "--expire") {
|
|
865
885
|
result.expire = true;
|
|
866
886
|
} else if (arg === "--role") {
|
|
867
|
-
// #2508 — a role the approver
|
|
868
|
-
// roles
|
|
887
|
+
// #2508 — a role the approver claims, which a gate's policy sees. A
|
|
888
|
+
// quorum reads roles from .chant/trust.json at base instead (#4290).
|
|
889
|
+
// Repeatable, and a comma list works too.
|
|
869
890
|
const value = args[++i] ?? "";
|
|
870
891
|
result.roles = [...(result.roles ?? []), ...value.split(",").map((r) => r.trim()).filter(Boolean)];
|
|
871
892
|
} else if (arg === "--agent") {
|
package/src/cli/registry.ts
CHANGED
|
@@ -302,6 +302,12 @@ export interface ParsedArgs {
|
|
|
302
302
|
* pr-apply finds it from the commit through `--forge` when omitted.
|
|
303
303
|
*/
|
|
304
304
|
pr?: number;
|
|
305
|
+
/** `chant lock --list` (#4293): print the target locks instead of taking any. */
|
|
306
|
+
list?: boolean;
|
|
307
|
+
/** `chant lock --stage plan|apply|lock` (#4293): how the lock is taken. Default `lock`. */
|
|
308
|
+
lockStage?: string;
|
|
309
|
+
/** `chant lock --from-report <file>` (#4293): lock the components a `pr-plan.json` lists. */
|
|
310
|
+
fromReport?: string;
|
|
305
311
|
/** Every `--pr <n>`, in order: `chant release compose` (#4168 4.3) takes several. */
|
|
306
312
|
prs?: number[];
|
|
307
313
|
/** `chant release cut --channel <tag>` (#4168 4.3): the tag a publish of the release moves in place of `latest`. */
|
|
@@ -345,6 +351,8 @@ export interface ParsedArgs {
|
|
|
345
351
|
* approved plans of the merges between or a gate of its own.
|
|
346
352
|
*/
|
|
347
353
|
green?: boolean;
|
|
354
|
+
/** `chant build --pr-loop --pr-env` (#4298) — add the env-up, env-down and env-sweep jobs of a pull request's environment. */
|
|
355
|
+
prEnv?: boolean;
|
|
348
356
|
/** `chant build --pr-loop --pr-build` (#4088) — build and lint before pr-plan and pr-apply. */
|
|
349
357
|
prBuild?: boolean;
|
|
350
358
|
/** `chant ci serve --build-command <line>` (#4169) — the line that builds a commit a `ci/build/<sha>` tag asks for, in a worktree of it. */
|
|
@@ -477,7 +485,7 @@ export interface ParsedArgs {
|
|
|
477
485
|
records?: boolean;
|
|
478
486
|
/** `chant workspace wip save --label <text>` (#3172): what the caller calls the snapshot, such as a turn id. */
|
|
479
487
|
label?: string;
|
|
480
|
-
/** `chant workspace wip --branch <branch>` (#3172): list one branch's snapshots. */
|
|
488
|
+
/** `chant workspace wip --branch <branch>` (#3172): list one branch's snapshots. `chant components pr-apply|pr-record --branch <branch>` (#4292): the branch pushed, which `ci.branches` routes members by; default the forge's GITHUB_REF_NAME or CI_COMMIT_BRANCH. */
|
|
481
489
|
branch?: string;
|
|
482
490
|
/** `chant ci workflow --workflow <name>`, repeatable (#3573): a workflow whose completion triggers a tick, beside those the scan finds. Also `chant ci init --workflow <name>` (#4168 step 6), the prescribed workflow to set up. */
|
|
483
491
|
ciWorkflows?: string[];
|
|
@@ -491,6 +499,8 @@ export interface ParsedArgs {
|
|
|
491
499
|
ciSecrets?: string;
|
|
492
500
|
/** `chant ci workflow --chant <command>` (#3573): the command the tick step runs chant with, in place of the npx pin. Also `chant ci run --chant` (#4094): what runs chant in the job. */
|
|
493
501
|
ciChant?: string;
|
|
502
|
+
/** `chant components pr-post --result <result>` (#4295): the plan job's own result. `failure` or `cancelled` fails the `chant/plan` status. */
|
|
503
|
+
planResult?: string;
|
|
494
504
|
/** `chant ci run|serve --no-status` (#4097): post no commit status for the job. `chant components pr-plan|pr-apply --no-status` (#4161): post neither the status nor the note. */
|
|
495
505
|
noStatus?: boolean;
|
|
496
506
|
/** `chant ci run|serve --status-url <template>` (#4097): the link a job's status carries. `{key}` is `<pr>/<commit>[/<member>]`, as `--report-url` keys it. */
|
|
@@ -737,7 +747,7 @@ export interface ParsedArgs {
|
|
|
737
747
|
* on a model-authored channel.
|
|
738
748
|
*/
|
|
739
749
|
allowSameOrigin?: boolean;
|
|
740
|
-
/** `chant approve --role <name>` (#2508) — roles the approver claims,
|
|
750
|
+
/** `chant approve --role <name>` (#2508) — roles the approver claims, which a gate's policy sees. A quorum never counts them (#4290): its roles come from `.chant/trust.json` at base. Repeatable; a comma list also works. */
|
|
741
751
|
roles?: string[];
|
|
742
752
|
/** `chant approve --agent` (#2508) — record the approval as an agent's. An agent never counts toward a quorum; it passes a gate only through a policy permit in `enforce` mode. */
|
|
743
753
|
agent?: boolean;
|
|
@@ -129,6 +129,19 @@ export interface FanOutWaveGate {
|
|
|
129
129
|
* do not exist yet. A later wave is left for its own job.
|
|
130
130
|
*/
|
|
131
131
|
only?: number;
|
|
132
|
+
/**
|
|
133
|
+
* With `only` (#4291): members of earlier waves the record says failed to
|
|
134
|
+
* plan or to apply. Each blocks its dependents in this wave and names
|
|
135
|
+
* itself as what blocked them; the rest of the wave runs. An earlier
|
|
136
|
+
* member that neither applied nor failed still refuses the run.
|
|
137
|
+
*/
|
|
138
|
+
failedEarlier?: readonly string[];
|
|
139
|
+
/**
|
|
140
|
+
* With `only` (#4291): earlier waves the record says stopped at their gate.
|
|
141
|
+
* A member left in one of them stops this wave too ({@link EarlierWaveWaitingError}),
|
|
142
|
+
* since a gate nobody approved stops every wave after it.
|
|
143
|
+
*/
|
|
144
|
+
waitingEarlier?: ReadonlyArray<{ wave: number; gate: string; components: readonly string[] }>;
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
/** Thrown when `FanOutWaveGate.only` names a wave whose predecessors have not all applied. */
|
|
@@ -146,6 +159,22 @@ export class EarlierWaveNotAppliedError extends Error {
|
|
|
146
159
|
}
|
|
147
160
|
}
|
|
148
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Thrown when `FanOutWaveGate.only` names a wave after one that waits at its
|
|
164
|
+
* gate (#4291): nothing in this wave runs until that gate is approved and
|
|
165
|
+
* its job runs again. The CLI exits 3, as the waiting wave's job does.
|
|
166
|
+
*/
|
|
167
|
+
export class EarlierWaveWaitingError extends Error {
|
|
168
|
+
constructor(
|
|
169
|
+
readonly wave: number,
|
|
170
|
+
readonly earlier: number,
|
|
171
|
+
readonly gate: string,
|
|
172
|
+
) {
|
|
173
|
+
super(`wave ${wave} does not run yet: wave ${earlier} waits at its gate ${gate}. Approve it and run wave ${earlier} again, then this wave.`);
|
|
174
|
+
this.name = "EarlierWaveWaitingError";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
149
178
|
/** The single gate over a whole fan-out. Omit it to run without one. */
|
|
150
179
|
export interface FanOutGate {
|
|
151
180
|
/** The name `chant approve <op> <gate>` takes — the fan-out's name, not a component's. */
|
|
@@ -518,7 +547,13 @@ async function runGatedWaves(
|
|
|
518
547
|
if (!wave) return { end: "stop" };
|
|
519
548
|
const runnable = wave.filter((name) => left.has(name) && !blockedBy.has(name));
|
|
520
549
|
if (waveGate.only !== undefined && waveNum < waveGate.only) {
|
|
521
|
-
|
|
550
|
+
// #4291: an earlier member that failed blocks only its dependents; one that waits at its gate stops this wave.
|
|
551
|
+
const failedBefore = new Set(waveGate.failedEarlier ?? []);
|
|
552
|
+
const open = runnable.filter((name) => !failedBefore.has(name));
|
|
553
|
+
const waiting = waveGate.waitingEarlier?.find((w) => w.wave === waveNum && open.some((name) => w.components.includes(name)));
|
|
554
|
+
if (waiting) throw new EarlierWaveWaitingError(waveGate.only, waveNum, waiting.gate);
|
|
555
|
+
if (open.length > 0) throw new EarlierWaveNotAppliedError(waveGate.only, waveNum, open);
|
|
556
|
+
for (const name of runnable) markBlocked(name);
|
|
522
557
|
return { end: "next" };
|
|
523
558
|
}
|
|
524
559
|
if (waveGate.only !== undefined && waveNum > waveGate.only) return { end: "stop" };
|
|
@@ -664,7 +699,9 @@ async function runGatedWaves(
|
|
|
664
699
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
665
700
|
.map(([component, by]) => ({ component, reason: "blocked" as const, blockedBy: by }));
|
|
666
701
|
|
|
667
|
-
|
|
702
|
+
// #4291: a member of this job's wave that an earlier job's failure blocked fails the job, though the rest of the wave applied.
|
|
703
|
+
const blockedHere = waveGate.only !== undefined && (plan.waves[waveGate.only - 1] ?? []).some((name) => blockedBy.has(name));
|
|
704
|
+
const status: FanOutRunResult["status"] = failed.length > 0 || blockedHere ? "fail" : gatedFact ? "gated" : "ok";
|
|
668
705
|
emit?.({ type: "run-settle", status: status === "fail" ? "failed" : status, durationMs: null });
|
|
669
706
|
|
|
670
707
|
return {
|
|
@@ -11,7 +11,7 @@ import { describe, expect, test } from "vitest";
|
|
|
11
11
|
import { CapabilityRegistry, type DeployContext } from "./capability";
|
|
12
12
|
import { memoryGateLedgerPort } from "../op/gate";
|
|
13
13
|
import { planFanOut } from "./fan-out";
|
|
14
|
-
import { EarlierWaveNotAppliedError, runFanOut, type FanOutRunOptions } from "./fan-out-run";
|
|
14
|
+
import { EarlierWaveNotAppliedError, EarlierWaveWaitingError, runFanOut, type FanOutRunOptions } from "./fan-out-run";
|
|
15
15
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
16
16
|
import type { DriverComponent } from "./driver";
|
|
17
17
|
import type { GateResolutionRecord, PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
@@ -354,6 +354,50 @@ describe("one wave per CI job", () => {
|
|
|
354
354
|
});
|
|
355
355
|
});
|
|
356
356
|
|
|
357
|
+
describe("a failed member blocks only its dependents across wave jobs (#4291)", () => {
|
|
358
|
+
/** net, then a and b, then app (reads a) and web (reads b). */
|
|
359
|
+
const WIDE: DriverComponent[] = [...ESTATE, root("web", ["b"])];
|
|
360
|
+
|
|
361
|
+
test("wave 3's job blocks what an earlier job's failure holds, and plans the rest", async () => {
|
|
362
|
+
const w = world();
|
|
363
|
+
const plan = planFanOut({ components: WIDE, changed: ["net"] });
|
|
364
|
+
const result = await runFanOut(plan, WIDE, w.registry, {
|
|
365
|
+
...waveOptions(ledger()),
|
|
366
|
+
waveGate: { op: "fan-out", gate: "release", only: 3, failedEarlier: ["a"] },
|
|
367
|
+
progress: { completed: ["net", "b"] },
|
|
368
|
+
});
|
|
369
|
+
expect(result.blocked).toEqual([{ component: "app", reason: "blocked", blockedBy: "a" }]);
|
|
370
|
+
expect(w.planned.map((p) => p.root)).toEqual(["web"]);
|
|
371
|
+
// A member of the job's own wave was blocked, so the job fails though web went on to its gate.
|
|
372
|
+
expect(result.status).toBe("fail");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("an earlier member that neither applied nor failed still refuses the job", async () => {
|
|
376
|
+
const w = world();
|
|
377
|
+
const plan = planFanOut({ components: WIDE, changed: ["net"] });
|
|
378
|
+
await expect(
|
|
379
|
+
runFanOut(plan, WIDE, w.registry, {
|
|
380
|
+
...waveOptions(ledger()),
|
|
381
|
+
waveGate: { op: "fan-out", gate: "release", only: 3, failedEarlier: ["a"] },
|
|
382
|
+
progress: { completed: ["net"] },
|
|
383
|
+
}),
|
|
384
|
+
).rejects.toThrow(EarlierWaveNotAppliedError);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("an earlier wave that waits at its gate stops the job", async () => {
|
|
388
|
+
const w = world();
|
|
389
|
+
const plan = planFanOut({ components: WIDE, changed: ["net"] });
|
|
390
|
+
await expect(
|
|
391
|
+
runFanOut(plan, WIDE, w.registry, {
|
|
392
|
+
...waveOptions(ledger()),
|
|
393
|
+
waveGate: { op: "fan-out", gate: "release", only: 3, waitingEarlier: [{ wave: 2, gate: "release-wave-2", components: ["a", "b"] }] },
|
|
394
|
+
progress: { completed: ["net"] },
|
|
395
|
+
}),
|
|
396
|
+
).rejects.toThrow(EarlierWaveWaitingError);
|
|
397
|
+
expect(w.planned).toEqual([]);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
357
401
|
describe("both modes", () => {
|
|
358
402
|
test("a run takes one gate over the set or one per wave, not both", async () => {
|
|
359
403
|
const w = world();
|