@intentius/chant 0.124.17 → 0.124.19
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/__golden__/wave-loop/fan-out-applied.json +511 -0
- package/dist/__golden__/wave-loop/fan-out-apply-fails.json +406 -0
- package/dist/__golden__/wave-loop/fan-out-gated.json +707 -0
- package/dist/__golden__/wave-loop/fan-out-no-members.json +898 -0
- package/dist/__golden__/wave-loop/fan-out-only.json +514 -0
- package/dist/__golden__/wave-loop/fan-out-plan-fails.json +359 -0
- package/dist/__golden__/wave-loop/fan-out-same-root.json +311 -0
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +401 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +756 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +340 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +400 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +404 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +62 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +191 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +407 -0
- package/dist/__golden__/wave-loop/op-denied.json +399 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +92 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +340 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +659 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +290 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +176 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +689 -0
- package/dist/__golden__/wave-loop/op-held-on.json +1455 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +673 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +737 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +450 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +97 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +1059 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +693 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +735 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +148 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +1225 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +202 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +286 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +823 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +259 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +196 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +193 -0
- package/dist/__golden__/wave-loop/op-overridden.json +508 -0
- package/dist/__golden__/wave-loop/op-per-run.json +518 -0
- package/dist/__golden__/wave-loop/op-point.json +495 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +397 -0
- package/dist/__golden__/wave-loop/op-sealed.json +125 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +168 -0
- package/dist/__golden__/wave-loop/op-spend.json +328 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +1808 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +291 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +231 -0
- package/dist/__golden__/wave-loop/op-wide.json +440 -0
- package/dist/cli/commands/import.d.ts +2 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +6 -2
- package/dist/cli/handlers/graph.js +24 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +2 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +4 -0
- package/dist/cli/registry.d.ts +2 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/fan-out-run.d.ts +4 -3
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +138 -123
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +9 -3
- package/dist/graph-dot.d.ts.map +1 -1
- package/dist/graph-dot.js +36 -2
- package/dist/graph-ir.d.ts +98 -3
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +71 -0
- package/dist/graph-lens.js +12 -1
- package/dist/graph-mermaid.d.ts +5 -1
- package/dist/graph-mermaid.d.ts.map +1 -1
- package/dist/graph-mermaid.js +46 -2
- package/dist/lexicon.d.ts +22 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/op/op-waves-run.d.ts +7 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +129 -72
- package/dist/wave-engine.d.ts +74 -5
- package/dist/wave-engine.d.ts.map +1 -1
- package/dist/wave-engine.js +47 -5
- package/dist/wave-runtime.mjs +215 -215
- package/package.json +1 -1
- package/src/__golden__/wave-loop/fan-out-applied.json +511 -0
- package/src/__golden__/wave-loop/fan-out-apply-fails.json +406 -0
- package/src/__golden__/wave-loop/fan-out-gated.json +707 -0
- package/src/__golden__/wave-loop/fan-out-no-members.json +898 -0
- package/src/__golden__/wave-loop/fan-out-only.json +514 -0
- package/src/__golden__/wave-loop/fan-out-plan-fails.json +359 -0
- package/src/__golden__/wave-loop/fan-out-same-root.json +311 -0
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +401 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +756 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +340 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +400 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +404 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +62 -0
- package/src/__golden__/wave-loop/op-cost-member.json +191 -0
- package/src/__golden__/wave-loop/op-denied-source.json +407 -0
- package/src/__golden__/wave-loop/op-denied.json +399 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +92 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +340 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +659 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +290 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +176 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +689 -0
- package/src/__golden__/wave-loop/op-held-on.json +1455 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +673 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +737 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +450 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +97 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +1059 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +693 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +735 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +148 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +1225 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +202 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +286 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +823 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +259 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +196 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +193 -0
- package/src/__golden__/wave-loop/op-overridden.json +508 -0
- package/src/__golden__/wave-loop/op-per-run.json +518 -0
- package/src/__golden__/wave-loop/op-point.json +495 -0
- package/src/__golden__/wave-loop/op-pr-review.json +397 -0
- package/src/__golden__/wave-loop/op-sealed.json +125 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +168 -0
- package/src/__golden__/wave-loop/op-spend.json +328 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +1808 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +291 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +231 -0
- package/src/__golden__/wave-loop/op-wide.json +440 -0
- package/src/cli/commands/import-live.test.ts +24 -0
- package/src/cli/commands/import.ts +8 -2
- package/src/cli/handlers/graph.test.ts +23 -0
- package/src/cli/handlers/graph.ts +22 -1
- package/src/cli/handlers/misc.ts +2 -1
- package/src/cli/main.test.ts +5 -0
- package/src/cli/parse-args.ts +3 -0
- package/src/cli/registry.ts +2 -0
- package/src/codegen/publish-order.test.ts +118 -4
- package/src/components/fan-out-run.ts +156 -128
- package/src/graph-ci-edges.test.ts +97 -0
- package/src/graph-detail.ts +9 -3
- package/src/graph-dot.ts +34 -2
- package/src/graph-ir.ts +156 -3
- package/src/graph-lens.ts +10 -1
- package/src/graph-mermaid.ts +44 -2
- package/src/lexicon.ts +23 -1
- package/src/meta/peer-deps.test.ts +39 -0
- package/src/op/op-waves-run.ts +184 -68
- package/src/wave-engine.test.ts +38 -1
- package/src/wave-engine.ts +101 -5
- package/src/wave-loop-golden.test.ts +627 -0
package/dist/op/op-waves-run.js
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
51
51
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
52
52
|
import { dirname, isAbsolute, join, posix } from "node:path";
|
|
53
|
-
import { decideWaveGate, waveDigestMembers, waveSetDigest, writeWaveRecordFile } from "../wave-engine.js";
|
|
53
|
+
import { decideWaveGate, runWaveLoop, waveDigestMembers, waveSetDigest, writeWaveRecordFile, } from "../wave-engine.js";
|
|
54
54
|
import { approveCommand, evaluateGate, gitGateLedgerPort } from "./gate.js";
|
|
55
55
|
import { isApprovalSpent } from "../lifecycle/gate-applied.js";
|
|
56
56
|
import { OP_WAVE_DEFAULT_APPLY, OP_WAVE_GATE_POLICIES, OP_WAVE_HOOK_MOMENTS, GATE_APPROVAL_SOURCES, assertOpWaveHooks, assertOpWavesSpec, opWaveApplyHooksRunBy, opWaveGateName, opWaveHooksAt, opWavePointAt, opWavePointProblems, opWaveRecordPath, opWaveShare, } from "./op-waves.js";
|
|
@@ -710,7 +710,13 @@ function noteHooksAtBase(ctx, rules) {
|
|
|
710
710
|
ctx.log(`wave ${ctx.k} (${ctx.wave.name}): its hooks are read at ${base.slice(0, 12)}, and this change's differ; the ones at base run`);
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
|
-
/**
|
|
713
|
+
/**
|
|
714
|
+
* Plan, decide and apply one wave, or one part of a wide wave: the wave
|
|
715
|
+
* engine's loop (`runWaveLoop`, #3766) over this module's steps. An apply
|
|
716
|
+
* that comes back `replan` plans the wave again (#3821), at most
|
|
717
|
+
* {@link OP_WAVE_MAX_REPLANS} times; a `rest` wave goes on through the waves
|
|
718
|
+
* its plan command found.
|
|
719
|
+
*/
|
|
714
720
|
export async function runOpWave(options) {
|
|
715
721
|
const { spec } = options;
|
|
716
722
|
assertOpWavesSpec(spec);
|
|
@@ -718,37 +724,47 @@ export async function runOpWave(options) {
|
|
|
718
724
|
const last = spec.waves[spec.waves.length - 1];
|
|
719
725
|
const applied = [];
|
|
720
726
|
const rest = [];
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
727
|
+
const result = await runWaveLoop({
|
|
728
|
+
first: options.wave,
|
|
729
|
+
plan: (at) => planOpWave(options, at),
|
|
730
|
+
// A share job applies under the digest its deciding job recorded; it does not take the set digest again.
|
|
731
|
+
digest: (_at, planned) => (planned.kind === "share" ? planned.decision.digest : opWaveSetDigest(spec, planned.members, planned.extras)),
|
|
732
|
+
decide: (at, planned, digest) => (planned.kind === "share" ? shareUnmoved(planned) : decideOpWave(options, at, planned, digest)),
|
|
733
|
+
apply: (_at, planned, decided) => applyOpWave(options, planned, decided),
|
|
734
|
+
replan: (_at, outcome) => outcome.replan !== undefined,
|
|
735
|
+
maxPasses: OP_WAVE_MAX_REPLANS,
|
|
736
|
+
passesSpent({ k }, outcome) {
|
|
737
|
+
log(`wave ${k}: its apply asked to plan again ${OP_WAVE_MAX_REPLANS} times, so the wave stops here`);
|
|
738
|
+
outcome.exitCode = 1;
|
|
739
|
+
return outcome;
|
|
740
|
+
},
|
|
741
|
+
replanning({ k }, outcome) {
|
|
742
|
+
log(`wave ${k}: the apply applied nothing and asks to plan again${outcome.replan.reason ? ` (${outcome.replan.reason})` : ""}; planning again`);
|
|
743
|
+
},
|
|
744
|
+
onward(k, outcome) {
|
|
745
|
+
applied.push(...outcome.applied);
|
|
746
|
+
// #3821: a `rest` wave goes on through the waves its plan command found.
|
|
747
|
+
const found = outcome.decision?.discovered?.waves;
|
|
748
|
+
const onward = k >= spec.waves.length && last.rest === true && options.share === undefined && !options.decide && outcome.exitCode === 0 && found !== undefined && k < found;
|
|
749
|
+
if (rest.length > 0 || onward)
|
|
750
|
+
rest.push({ wave: k, exitCode: outcome.exitCode, ...(outcome.decision ? { digest: outcome.decision.digest } : {}) });
|
|
751
|
+
if (onward)
|
|
752
|
+
log(`wave ${k} applied; the plan found ${found} waves, so this job goes on to wave ${k + 1}`);
|
|
753
|
+
return onward;
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
return { ...result, applied, ...(rest.length > 0 ? { rest } : {}) };
|
|
747
757
|
}
|
|
748
758
|
/** How many times one wave job plans again when its apply asks to (#3821). */
|
|
749
759
|
export const OP_WAVE_MAX_REPLANS = 10;
|
|
750
|
-
/**
|
|
751
|
-
|
|
760
|
+
/**
|
|
761
|
+
* Plan wave `k`: read its rules at base and run the before-plan hooks
|
|
762
|
+
* around planning its runs, or for a share job, read the deciding job's
|
|
763
|
+
* decision and plan the share's slice between its hooks. Ends the wave when
|
|
764
|
+
* a hook fails or holds it on another gate, or a share's decision waits or
|
|
765
|
+
* was refused.
|
|
766
|
+
*/
|
|
767
|
+
async function planOpWave(options, { k, pass }) {
|
|
752
768
|
const { spec } = options;
|
|
753
769
|
const cwd = options.cwd ?? process.cwd();
|
|
754
770
|
const exec = options.exec ?? spawnExec;
|
|
@@ -779,7 +795,7 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
779
795
|
ctx.gateRule = { approval: decision.approval, base: decision.base, neverOverMcp: decision.neverOverMcp === true };
|
|
780
796
|
if (decision.status === "waiting" || decision.status === "refused") {
|
|
781
797
|
log(`wave ${k} (${wave.name}) is ${decision.status === "refused" ? "refused by its policy" : `waiting on ${decision.heldOn?.gate ?? decision.gate}`}; nothing applied. ${decision.approve ?? ""}`.trim());
|
|
782
|
-
return { ...result, exitCode: decision.status === "refused" ? refusedExit(decision) : 3 };
|
|
798
|
+
return { end: { ...result, exitCode: decision.status === "refused" ? refusedExit(decision) : 3 } };
|
|
783
799
|
}
|
|
784
800
|
// The share runs the hooks its deciding job read at base, around its own slice.
|
|
785
801
|
if (wave.discover) {
|
|
@@ -790,32 +806,17 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
790
806
|
const slice = opWaveShare(wave.runs, options.share, shares);
|
|
791
807
|
const before = await runHooks(ctx, "before-plan", decision.hooks?.["before-plan"], slice);
|
|
792
808
|
if (before.failed)
|
|
793
|
-
return failed(ctx, `a hook before the plan failed, so nothing in share ${options.share} of wave ${k} was planned or applied`);
|
|
809
|
+
return { end: failed(ctx, `a hook before the plan failed, so nothing in share ${options.share} of wave ${k} was planned or applied`) };
|
|
794
810
|
if (before.waitingOn)
|
|
795
|
-
return waitOnOther(ctx, decision, before.waitingOn);
|
|
811
|
+
return { end: await waitOnOther(ctx, decision, before.waitingOn) };
|
|
796
812
|
const { members } = planRuns(ctx, slice);
|
|
797
813
|
const planned = new Map(members.map((m) => [m.member, m]));
|
|
798
814
|
const after = await runHooks(ctx, "after-plan", decision.hooks?.["after-plan"], slice, planned);
|
|
799
815
|
if (after.failed)
|
|
800
|
-
return failed(ctx, `a hook after the plan failed, so nothing in share ${options.share} of wave ${k} was applied`);
|
|
816
|
+
return { end: failed(ctx, `a hook after the plan failed, so nothing in share ${options.share} of wave ${k} was applied`) };
|
|
801
817
|
if (after.waitingOn)
|
|
802
|
-
return waitOnOther(ctx, decision, after.waitingOn);
|
|
803
|
-
|
|
804
|
-
const unmoved = [];
|
|
805
|
-
for (const run of slice) {
|
|
806
|
-
const now = planned.get(run.target);
|
|
807
|
-
if (decided.get(run.target) !== now.planDigest) {
|
|
808
|
-
log(`${run.target}: its plan moved since wave ${k} was decided, so it was not applied. ` +
|
|
809
|
-
`decided: ${decided.get(run.target) ?? "(not in the decision)"}; planned now: ${now.planDigest}`);
|
|
810
|
-
result.moved.push(run.target);
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
unmoved.push(run);
|
|
814
|
-
}
|
|
815
|
-
await applyStage(options, ctx, decision, unmoved, planned, [...before.holds, ...after.holds]);
|
|
816
|
-
if (result.exitCode === 0 && result.moved.length > 0)
|
|
817
|
-
result.exitCode = 4;
|
|
818
|
-
return result;
|
|
818
|
+
return { end: await waitOnOther(ctx, decision, after.waitingOn) };
|
|
819
|
+
return { go: { kind: "share", ctx, decision, slice, planned, holds: [...before.holds, ...after.holds] } };
|
|
819
820
|
}
|
|
820
821
|
// The policy, the approval mode and the hooks, read at base before anything runs.
|
|
821
822
|
const rules = readOpWavePolicy(options.specFile, wave.name, options.base ?? spec.base ?? "HEAD^1", cwd, options.show);
|
|
@@ -825,7 +826,7 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
825
826
|
noteHooksAtBase(ctx, rules);
|
|
826
827
|
// #3836: a point at base that cannot be asked fails the wave; the ledger never stands in for it.
|
|
827
828
|
if (rules.pointRefused)
|
|
828
|
-
return failed(ctx, `wave ${k} (${wave.name}): ${rules.pointRefused}, so nothing in it was planned or applied`);
|
|
829
|
+
return { end: failed(ctx, `wave ${k} (${wave.name}): ${rules.pointRefused}, so nothing in it was planned or applied`) };
|
|
829
830
|
const own = opWavePointAt(spec, wave.name);
|
|
830
831
|
if (JSON.stringify(own) !== JSON.stringify(rules.point)) {
|
|
831
832
|
log(`wave ${k} (${wave.name}): its gate's decision point is read at ${base.slice(0, 12)} (${rules.point?.name ?? "none"}), and this change's differs (${own?.name ?? "none"}); the one at base holds`);
|
|
@@ -858,9 +859,9 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
858
859
|
};
|
|
859
860
|
const before = await runHooks(ctx, "before-plan", rules.hooks["before-plan"], wave.runs);
|
|
860
861
|
if (before.failed)
|
|
861
|
-
return failed(ctx, `a hook before the plan failed, so nothing in wave ${k} was planned or applied`);
|
|
862
|
+
return { end: failed(ctx, `a hook before the plan failed, so nothing in wave ${k} was planned or applied`) };
|
|
862
863
|
if (before.waitingOn)
|
|
863
|
-
return decided(ctx, decision, recordPath, before.waitingOn);
|
|
864
|
+
return { end: await decided(ctx, decision, recordPath, before.waitingOn) };
|
|
864
865
|
const { members, pending, overriders, overridersSource, extras, waves: found } = planRuns(ctx, wave.runs, wave.discover === true);
|
|
865
866
|
if (wave.discover) {
|
|
866
867
|
wave = { ...wave, runs: members.map((m) => ({ target: m.member, ...(m.op !== spec.op ? { op: m.op } : {}) })) };
|
|
@@ -869,47 +870,96 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
869
870
|
log(`wave ${k} (${wave.name}): the plan found ${members.length} run${members.length === 1 ? "" : "s"}${found !== undefined ? ` of ${found} waves` : ""}${members.length ? `: ${members.map((m) => m.member).join(", ")}` : ""}`);
|
|
870
871
|
}
|
|
871
872
|
const planned = new Map(members.map((m) => [m.member, m]));
|
|
872
|
-
|
|
873
|
+
return {
|
|
874
|
+
go: {
|
|
875
|
+
kind: "decide",
|
|
876
|
+
ctx,
|
|
877
|
+
recordPath,
|
|
878
|
+
rules,
|
|
879
|
+
decision,
|
|
880
|
+
holds,
|
|
881
|
+
before: before.holds,
|
|
882
|
+
members,
|
|
883
|
+
planned,
|
|
884
|
+
...(pending ? { pending } : {}),
|
|
885
|
+
...(overriders ? { overriders } : {}),
|
|
886
|
+
...(overridersSource ? { overridersSource } : {}),
|
|
887
|
+
...(extras ? { extras } : {}),
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* A share job's decision is its deciding job's: it applies the runs of its
|
|
893
|
+
* slice whose plan has not moved since, and names each one that has.
|
|
894
|
+
*/
|
|
895
|
+
async function shareUnmoved(planned) {
|
|
896
|
+
const { ctx, decision, slice, holds } = planned;
|
|
897
|
+
const decidedDigests = new Map(decision.members.map((m) => [m.member, m.planDigest]));
|
|
898
|
+
const unmoved = [];
|
|
899
|
+
for (const run of slice) {
|
|
900
|
+
const now = planned.planned.get(run.target);
|
|
901
|
+
if (decidedDigests.get(run.target) !== now.planDigest) {
|
|
902
|
+
ctx.log(`${run.target}: its plan moved since wave ${ctx.k} was decided, so it was not applied. ` +
|
|
903
|
+
`decided: ${decidedDigests.get(run.target) ?? "(not in the decision)"}; planned now: ${now.planDigest}`);
|
|
904
|
+
ctx.result.moved.push(run.target);
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
unmoved.push(run);
|
|
908
|
+
}
|
|
909
|
+
return { go: { runs: unmoved, earlierHolds: holds } };
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Decide wave `k` against its set digest: run the after-plan hooks, settle
|
|
913
|
+
* the product's denials and the gate under the rules read at base, write the
|
|
914
|
+
* decision record and run the after-gate hooks. Ends the wave when it waits,
|
|
915
|
+
* is refused or failed, and for a deciding job (`--decide`), which spends the
|
|
916
|
+
* approval and overrides its shares apply under and applies nothing.
|
|
917
|
+
*/
|
|
918
|
+
async function decideOpWave(options, { prior }, planned, digest) {
|
|
919
|
+
const { ctx, recordPath, decision, holds, members, pending, overriders, overridersSource, extras } = planned;
|
|
920
|
+
const { k, wave, log, result } = ctx;
|
|
921
|
+
const { policy, source, base } = planned.rules;
|
|
873
922
|
decision.digest = digest;
|
|
874
923
|
decision.members = members;
|
|
875
924
|
if (extras)
|
|
876
925
|
decision.extras = extras;
|
|
877
926
|
if (pending)
|
|
878
927
|
decision.pending = pending;
|
|
879
|
-
const after = await runHooks(ctx, "after-plan", rules.hooks["after-plan"], wave.runs, planned, digest);
|
|
928
|
+
const after = await runHooks(ctx, "after-plan", planned.rules.hooks["after-plan"], wave.runs, planned.planned, digest);
|
|
880
929
|
if (after.failed)
|
|
881
|
-
return failed(ctx, `a hook after the plan failed, so nothing in wave ${k} was applied`);
|
|
882
|
-
holds.push(...before
|
|
930
|
+
return { end: failed(ctx, `a hook after the plan failed, so nothing in wave ${k} was applied`) };
|
|
931
|
+
holds.push(...planned.before, ...after.holds);
|
|
883
932
|
if (holds.length > 0)
|
|
884
933
|
decision.heldBy = holds;
|
|
885
934
|
if (after.waitingOn)
|
|
886
|
-
return decided(ctx, decision, recordPath, after.waitingOn);
|
|
935
|
+
return { end: await decided(ctx, decision, recordPath, after.waitingOn) };
|
|
887
936
|
// #3821: a run the product's policy denied applies only under an override; with none the wave is refused, and no approval is waited for.
|
|
888
|
-
const
|
|
937
|
+
const was = prior?.decision;
|
|
938
|
+
const kept = was !== undefined && (was.status === "approved" || was.status === "not-required") && was.digest === digest;
|
|
889
939
|
if (kept) {
|
|
890
940
|
// The overrides this job applied under, and spent, still let the same plans through.
|
|
891
|
-
const byRun = new Map(
|
|
941
|
+
const byRun = new Map(was.members.map((m) => [m.member, m.override]));
|
|
892
942
|
for (const m of members)
|
|
893
943
|
if (m.denied && byRun.get(m.member))
|
|
894
944
|
m.override = byRun.get(m.member);
|
|
895
|
-
if (
|
|
896
|
-
decision.overridesSpentAt =
|
|
945
|
+
if (was.overridesSpentAt)
|
|
946
|
+
decision.overridesSpentAt = was.overridesSpentAt;
|
|
897
947
|
}
|
|
898
948
|
if (members.some((m) => m.denied && !m.override))
|
|
899
949
|
await settleDenials(ctx, decision, overriders, overridersSource);
|
|
900
|
-
if (kept &&
|
|
950
|
+
if (kept && was.status === "approved" && decision.status !== "refused") {
|
|
901
951
|
// #3821: planned again after the apply asked to, to the same digest: the approval this job found still holds, and is not spent twice.
|
|
902
952
|
for (const key of ["via", "approvedBy", "approvedAt", "review", "spentAt"])
|
|
903
|
-
if (
|
|
904
|
-
decision[key] =
|
|
953
|
+
if (was[key] !== undefined)
|
|
954
|
+
decision[key] = was[key];
|
|
905
955
|
decision.status = "approved";
|
|
906
|
-
log(`wave ${k} (${wave.name}): planned again to the same digest, so the approval by ${
|
|
956
|
+
log(`wave ${k} (${wave.name}): planned again to the same digest, so the approval by ${was.approvedBy} still holds`);
|
|
907
957
|
}
|
|
908
958
|
else if (decision.status !== "refused" && (opWaveNeedsApproval(policy, members) || holds.length > 0))
|
|
909
959
|
await settleGate(options, ctx, decision);
|
|
910
960
|
const code = await decided(ctx, decision, recordPath);
|
|
911
961
|
if (code.exitCode !== 0)
|
|
912
|
-
return code;
|
|
962
|
+
return { end: code };
|
|
913
963
|
log(`wave ${k} (${wave.name}): gate ${policy} (read at ${base.slice(0, 12)}${source === "absent-at-base" ? ", where the wave is absent" : ""}), ` +
|
|
914
964
|
`digest ${digest}: ${decision.status}${decision.approvedBy ? ` by ${decision.approvedBy}` : ""}` +
|
|
915
965
|
(decision.via === "pr-review" ? ` (review of #${decision.review?.pr})` : "") +
|
|
@@ -917,13 +967,20 @@ async function runOneWave(options, k, prior, pass) {
|
|
|
917
967
|
// A wide wave's shares apply under the deciding job's approval and overrides, so they are spent here, once.
|
|
918
968
|
if (options.decide) {
|
|
919
969
|
if (!(await spendApproval(options, ctx, decision)) || !(await spendOverrides(ctx, decision)))
|
|
920
|
-
return ctx.result;
|
|
970
|
+
return { end: ctx.result };
|
|
921
971
|
if (decision.spentAt || decision.overridesSpentAt)
|
|
922
972
|
writeRecord(recordPath, decision, result);
|
|
923
|
-
return result;
|
|
973
|
+
return { end: result };
|
|
924
974
|
}
|
|
925
|
-
|
|
926
|
-
|
|
975
|
+
return { go: { runs: wave.runs, earlierHolds: [], recordPath } };
|
|
976
|
+
}
|
|
977
|
+
/** Apply a decided wave, or a share's unmoved runs (exit 4 when any moved). */
|
|
978
|
+
async function applyOpWave(options, planned, decidedWave) {
|
|
979
|
+
const { ctx, decision } = planned;
|
|
980
|
+
await applyStage(options, ctx, decision, decidedWave.runs, planned.planned, decidedWave.earlierHolds, decidedWave.recordPath);
|
|
981
|
+
if (planned.kind === "share" && ctx.result.exitCode === 0 && ctx.result.moved.length > 0)
|
|
982
|
+
ctx.result.exitCode = 4;
|
|
983
|
+
return ctx.result;
|
|
927
984
|
}
|
|
928
985
|
/**
|
|
929
986
|
* The gate has decided (#3821): write the decision record, run the
|
package/dist/wave-engine.d.ts
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* - The share split of a wide wave: {@link waveShare}.
|
|
9
9
|
* - The gate decision call: {@link decideWaveGate}.
|
|
10
10
|
* - The record file a wave job hands the next one: {@link writeWaveRecordFile}.
|
|
11
|
+
* - The loop itself: {@link runWaveLoop} plans a wave, takes its set digest,
|
|
12
|
+
* decides its gate and applies it, then goes on to the next wave or plans
|
|
13
|
+
* the same one again, as the entry point's steps say.
|
|
11
14
|
*
|
|
12
15
|
* The parts that need neither the gate ledger nor the filesystem live in
|
|
13
16
|
* `./gated-waves.ts`, which terragucci bundles on its own (#3421), and
|
|
@@ -15,11 +18,12 @@
|
|
|
15
18
|
* use it without the change-set code; this module re-exports both so a
|
|
16
19
|
* runner imports the engine from one place.
|
|
17
20
|
*
|
|
18
|
-
* What stays with each entry point
|
|
19
|
-
* commands, or each component's
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* What stays with each entry point, as the steps it hands {@link runWaveLoop}:
|
|
22
|
+
* how a wave is planned (Op runs' plan commands, or each component's
|
|
23
|
+
* capabilities), which members its digest covers, the gate policy, approval
|
|
24
|
+
* mode, decision point, hooks and spend rule (Op waves only), how it applies,
|
|
25
|
+
* and the shape of the record each writes. Those shapes are read by jobs and
|
|
26
|
+
* approvals already in the wild, so the engine writes what it is handed.
|
|
23
27
|
*/
|
|
24
28
|
import { type GateCheck, type GateLedgerPort } from "./op/gate.js";
|
|
25
29
|
import type { AppliedGateRecord } from "./lifecycle/gate-applied.js";
|
|
@@ -59,4 +63,69 @@ export declare function decideWaveGate(port: GateLedgerPort, request: WaveGateRe
|
|
|
59
63
|
* through it.
|
|
60
64
|
*/
|
|
61
65
|
export declare function writeWaveRecordFile(path: string, value: unknown): void;
|
|
66
|
+
/**
|
|
67
|
+
* What a step hands {@link runWaveLoop}: `go` carries a value into the
|
|
68
|
+
* wave's next step; `end` ends the wave with its outcome, and nothing after
|
|
69
|
+
* that step runs for it.
|
|
70
|
+
*/
|
|
71
|
+
export type WaveStep<T, R> = {
|
|
72
|
+
go: T;
|
|
73
|
+
} | {
|
|
74
|
+
end: R;
|
|
75
|
+
};
|
|
76
|
+
/** Where the loop is: wave `k` (1-based), its `pass`, and the outcome of the pass before when the wave is planned again. */
|
|
77
|
+
export interface WaveAttempt<R> {
|
|
78
|
+
k: number;
|
|
79
|
+
/** 0 on a wave's first plan. */
|
|
80
|
+
pass: number;
|
|
81
|
+
/** The outcome of the pass that asked to plan again. */
|
|
82
|
+
prior?: R;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The steps an entry point hands {@link runWaveLoop}, typed by what planning
|
|
86
|
+
* gives (`P`), what deciding gives (`D`) and a wave's outcome (`R`).
|
|
87
|
+
*
|
|
88
|
+
* The two entry points differ in each step, on purpose:
|
|
89
|
+
*
|
|
90
|
+
* - `chant run wave` (Op waves) plans with argv commands and hooks, takes
|
|
91
|
+
* the digest under `digest.over` with the plans file's extras, decides
|
|
92
|
+
* with the approval mode, decision point, spend rule, denials and pending
|
|
93
|
+
* fields read at base, and runs one wave per job (a `rest` wave walks on,
|
|
94
|
+
* an apply may ask to plan again; a share job applies under its deciding
|
|
95
|
+
* job's decision).
|
|
96
|
+
* - `chant components fan-out --wave-gate` plans each component's
|
|
97
|
+
* capabilities, takes the digest over every member (a wave where nothing
|
|
98
|
+
* planned has the empty digest, and one where nothing is left to run skips
|
|
99
|
+
* its gate), asks the plain ledger, and walks every wave in one job.
|
|
100
|
+
*/
|
|
101
|
+
export interface WaveLoopSteps<P, D, R> {
|
|
102
|
+
/** The first wave's number. */
|
|
103
|
+
first: number;
|
|
104
|
+
/** Plan the wave, or end it before it has a set (nothing to run, a hook failed or waits). */
|
|
105
|
+
plan(at: WaveAttempt<R>): Promise<WaveStep<P, R>>;
|
|
106
|
+
/** The wave's set digest over what was planned. */
|
|
107
|
+
digest(at: WaveAttempt<R>, planned: P): string;
|
|
108
|
+
/** Decide the wave's gate against its digest: what the apply goes ahead under, or the outcome when the wave stops here. */
|
|
109
|
+
decide(at: WaveAttempt<R>, planned: P, digest: string): Promise<WaveStep<D, R>>;
|
|
110
|
+
/** Apply the decided wave. */
|
|
111
|
+
apply(at: WaveAttempt<R>, planned: P, decided: D): Promise<R>;
|
|
112
|
+
/** Whether the outcome asks for the same wave to be planned again in this job. Absent: never. */
|
|
113
|
+
replan?(at: WaveAttempt<R>, outcome: R): boolean;
|
|
114
|
+
/** How many passes one wave gets before a replan is refused. Default 1. */
|
|
115
|
+
maxPasses?: number;
|
|
116
|
+
/** The wave asked to plan again after its last pass: the outcome it ends with. */
|
|
117
|
+
passesSpent?(at: WaveAttempt<R>, outcome: R): R;
|
|
118
|
+
/** Called before another pass at the same wave. */
|
|
119
|
+
replanning?(at: WaveAttempt<R>, outcome: R): void;
|
|
120
|
+
/** The wave ended with `outcome`: whether the job goes on to wave `k + 1`. */
|
|
121
|
+
onward(k: number, outcome: R): boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The wave loop both entry points run (#3766): for each wave, plan it, take
|
|
125
|
+
* its set digest, decide its gate and apply it; plan it again while its
|
|
126
|
+
* outcome asks to, up to `maxPasses`; then go on to the next wave while
|
|
127
|
+
* `onward` says so. Returns the outcome of the last wave it ran. A step that
|
|
128
|
+
* throws ends the loop with that error.
|
|
129
|
+
*/
|
|
130
|
+
export declare function runWaveLoop<P, D, R>(steps: WaveLoopSteps<P, D, R>): Promise<R>;
|
|
62
131
|
//# sourceMappingURL=wave-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wave-engine.d.ts","sourceRoot":"","sources":["../src/wave-engine.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"wave-engine.d.ts","sourceRoot":"","sources":["../src/wave-engine.ts"],"names":[],"mappings":"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"}
|
package/dist/wave-engine.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* - The share split of a wide wave: {@link waveShare}.
|
|
9
9
|
* - The gate decision call: {@link decideWaveGate}.
|
|
10
10
|
* - The record file a wave job hands the next one: {@link writeWaveRecordFile}.
|
|
11
|
+
* - The loop itself: {@link runWaveLoop} plans a wave, takes its set digest,
|
|
12
|
+
* decides its gate and applies it, then goes on to the next wave or plans
|
|
13
|
+
* the same one again, as the entry point's steps say.
|
|
11
14
|
*
|
|
12
15
|
* The parts that need neither the gate ledger nor the filesystem live in
|
|
13
16
|
* `./gated-waves.ts`, which terragucci bundles on its own (#3421), and
|
|
@@ -15,11 +18,12 @@
|
|
|
15
18
|
* use it without the change-set code; this module re-exports both so a
|
|
16
19
|
* runner imports the engine from one place.
|
|
17
20
|
*
|
|
18
|
-
* What stays with each entry point
|
|
19
|
-
* commands, or each component's
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* What stays with each entry point, as the steps it hands {@link runWaveLoop}:
|
|
22
|
+
* how a wave is planned (Op runs' plan commands, or each component's
|
|
23
|
+
* capabilities), which members its digest covers, the gate policy, approval
|
|
24
|
+
* mode, decision point, hooks and spend rule (Op waves only), how it applies,
|
|
25
|
+
* and the shape of the record each writes. Those shapes are read by jobs and
|
|
26
|
+
* approvals already in the wild, so the engine writes what it is handed.
|
|
23
27
|
*/
|
|
24
28
|
import { dirname } from "node:path";
|
|
25
29
|
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
@@ -57,3 +61,41 @@ export function writeWaveRecordFile(path, value) {
|
|
|
57
61
|
writeFileSync(temporary, JSON.stringify(value, null, 2) + "\n");
|
|
58
62
|
renameSync(temporary, path);
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The wave loop both entry points run (#3766): for each wave, plan it, take
|
|
66
|
+
* its set digest, decide its gate and apply it; plan it again while its
|
|
67
|
+
* outcome asks to, up to `maxPasses`; then go on to the next wave while
|
|
68
|
+
* `onward` says so. Returns the outcome of the last wave it ran. A step that
|
|
69
|
+
* throws ends the loop with that error.
|
|
70
|
+
*/
|
|
71
|
+
export async function runWaveLoop(steps) {
|
|
72
|
+
for (let k = steps.first;; k++) {
|
|
73
|
+
let outcome;
|
|
74
|
+
let prior;
|
|
75
|
+
for (let pass = 0;; pass++) {
|
|
76
|
+
const at = { k, pass, ...(prior !== undefined ? { prior } : {}) };
|
|
77
|
+
outcome = await runWave(steps, at);
|
|
78
|
+
if (!steps.replan?.(at, outcome))
|
|
79
|
+
break;
|
|
80
|
+
if (pass + 1 >= (steps.maxPasses ?? 1)) {
|
|
81
|
+
outcome = steps.passesSpent ? steps.passesSpent(at, outcome) : outcome;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
steps.replanning?.(at, outcome);
|
|
85
|
+
prior = outcome;
|
|
86
|
+
}
|
|
87
|
+
if (!steps.onward(k, outcome))
|
|
88
|
+
return outcome;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** One pass at one wave: plan, digest, decide, apply. */
|
|
92
|
+
async function runWave(steps, at) {
|
|
93
|
+
const planned = await steps.plan(at);
|
|
94
|
+
if ("end" in planned)
|
|
95
|
+
return planned.end;
|
|
96
|
+
const digest = steps.digest(at, planned.go);
|
|
97
|
+
const decided = await steps.decide(at, planned.go, digest);
|
|
98
|
+
if ("end" in decided)
|
|
99
|
+
return decided.end;
|
|
100
|
+
return steps.apply(at, planned.go, decided.go);
|
|
101
|
+
}
|