stageflow 0.16.0 → 0.19.0
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/README.md +2 -4
- package/dist/agent/activity.d.ts +4 -0
- package/dist/agent/claudeAdapter.js +38 -0
- package/dist/agent/claudeTools.d.ts +1 -4
- package/dist/agent/claudeTools.js +1 -6
- package/dist/agent/fakeAgent.js +7 -6
- package/dist/agent/piAdapter.js +34 -15
- package/dist/agent/port.d.ts +1 -1
- package/dist/agent/stageTimeout.d.ts +9 -0
- package/dist/agent/stageTimeout.js +24 -0
- package/dist/cli/graphCommand.d.ts +15 -0
- package/dist/cli/graphCommand.js +79 -0
- package/dist/cli/graphRender.d.ts +2 -0
- package/dist/cli/graphRender.js +85 -0
- package/dist/cli/runsCommand.d.ts +1 -1
- package/dist/cli/runsCommand.js +37 -0
- package/dist/cli.js +12 -0
- package/dist/config/cloneChain.d.ts +12 -0
- package/dist/config/cloneChain.js +322 -0
- package/dist/config/createPipeline.js +101 -21
- package/dist/config/createStage.js +7 -0
- package/dist/config/loadOutcome.d.ts +1 -0
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +34 -22
- package/dist/config/loadStage.d.ts +2 -0
- package/dist/config/loadStage.js +135 -125
- package/dist/config/migrateYaml.js +14 -3
- package/dist/config/normalizePipelineStageEntry.d.ts +5 -9
- package/dist/config/normalizePipelineStageEntry.js +133 -60
- package/dist/config/parsePreEmitChecks.js +2 -2
- package/dist/config/pipelineNeeds.d.ts +15 -2
- package/dist/config/pipelineNeeds.js +86 -4
- package/dist/config/pipelineRoute.d.ts +46 -0
- package/dist/config/pipelineRoute.js +215 -0
- package/dist/config/pipelineStageKeys.d.ts +2 -0
- package/dist/config/pipelineStageKeys.js +15 -4
- package/dist/config/printTargetYaml.js +18 -12
- package/dist/config/resolveForkEmitContext.d.ts +1 -1
- package/dist/config/resolveForkEmitContext.js +3 -26
- package/dist/config/resolvePipelineDag.d.ts +1 -3
- package/dist/config/resolvePipelineDag.js +143 -186
- package/dist/config/routeIf.d.ts +24 -0
- package/dist/config/routeIf.js +247 -0
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +5 -3
- package/dist/config/yamlDialect.d.ts +5 -8
- package/dist/config/yamlDialect.js +37 -29
- package/dist/envelope/check.d.ts +0 -2
- package/dist/envelope/check.js +1 -57
- package/dist/envelope/feedbackLoop.js +0 -3
- package/dist/envelope/forkChoice.js +1 -1
- package/dist/envelope/payloadSchema.d.ts +14 -0
- package/dist/envelope/payloadSchema.js +87 -2
- package/dist/mcp/catalogTools.js +1 -2
- package/dist/mcp/controlTools.js +17 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +0 -2
- package/dist/projection/projectRun.js +0 -3
- package/dist/prompt/priorEnvelope.js +1 -1
- package/dist/runstore/port.js +2 -0
- package/dist/runstore/stageInstanceId.d.ts +1 -0
- package/dist/runstore/stageInstanceId.js +9 -0
- package/dist/runtime/cloneSchedule.d.ts +16 -33
- package/dist/runtime/cloneSchedule.js +155 -236
- package/dist/runtime/envelopeRouting.js +43 -94
- package/dist/runtime/forkGeneration.d.ts +6 -2
- package/dist/runtime/forkGeneration.js +45 -15
- package/dist/runtime/joinReadiness.d.ts +16 -0
- package/dist/runtime/joinReadiness.js +156 -0
- package/dist/runtime/pipelineScheduler.d.ts +0 -3
- package/dist/runtime/pipelineScheduler.js +103 -65
- package/dist/runtime/reopenSkipped.d.ts +13 -0
- package/dist/runtime/reopenSkipped.js +56 -0
- package/dist/runtime/resumeTimedOut.d.ts +33 -0
- package/dist/runtime/resumeTimedOut.js +165 -0
- package/dist/runtime/routeIfEval.d.ts +3 -0
- package/dist/runtime/routeIfEval.js +94 -0
- package/dist/runtime/runManager.d.ts +1 -0
- package/dist/runtime/runManager.js +76 -0
- package/dist/runtime/runRetryCoordinator.js +0 -4
- package/dist/runtime/stageAttemptBootstrap.js +60 -11
- package/dist/runtime/stageRecovery.js +2 -2
- package/dist/runtime/stageRunner.js +6 -2
- package/dist/runtime/taskInput.js +1 -13
- package/dist/server/http.js +18 -0
- package/dist/tools/askOperator.d.ts +1 -0
- package/dist/tools/askOperator.js +14 -11
- package/dist/tools/emitStageEnvelope.d.ts +3 -3
- package/dist/tools/emitStageEnvelope.js +4 -66
- package/dist/tools/prepareToolArguments.d.ts +4 -0
- package/dist/tools/prepareToolArguments.js +79 -0
- package/dist/types/envelope.d.ts +0 -2
- package/dist/types/forkChoice.d.ts +0 -16
- package/dist/types/pipeline.d.ts +86 -27
- package/dist/types/stage.d.ts +11 -7
- package/dist/ui/assets/{index-D2Wt8hwH.css → index-CSsDued1.css} +1 -1
- package/dist/ui/assets/index-DMVdl1qB.js +119 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/stageflow-author/SKILL.md +4 -4
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -2
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -4
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +6 -4
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +9 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +3 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +8 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +3 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +8 -0
- package/skills/stageflow-author/references/catalog-mapping.md +159 -50
- package/skills/stageflow-author/references/catalog-write-conventions.md +3 -3
- package/skills/stageflow-author/references/stage-prompt-template.md +57 -17
- package/skills/stageflow-session-capture/SKILL.md +2 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +3 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +15 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +3 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -2
- package/dist/envelope/cloneForks.d.ts +0 -2
- package/dist/envelope/cloneForks.js +0 -77
- package/dist/ui/assets/index-XWJovnp7.js +0 -118
|
@@ -10,22 +10,35 @@ function asDagSnapshot(dag) {
|
|
|
10
10
|
stage_ids: dag.nodes.map((n) => n.id),
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
function nodeById(dag, id) {
|
|
14
|
+
return dag.nodes.find((n) => n.id === id);
|
|
15
|
+
}
|
|
16
|
+
function isCloneEmitter(node) {
|
|
17
|
+
return node?.clone_array_field !== undefined;
|
|
18
|
+
}
|
|
19
|
+
function isMintedCloneInstance(node) {
|
|
20
|
+
return (node !== undefined &&
|
|
21
|
+
node.definition_id !== undefined &&
|
|
22
|
+
node.definition_id !== node.id);
|
|
23
|
+
}
|
|
24
|
+
function parentIsCloneEmitter(dag, node) {
|
|
25
|
+
if (node?.needs === null || node?.needs === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
return isCloneEmitter(nodeById(dag, node.needs));
|
|
28
|
+
}
|
|
29
|
+
/** Persistent route stages that fan out to Clone Chain children (or already have instances). */
|
|
14
30
|
export function routeRelatedForkParentIds(dag, routeStageIds) {
|
|
15
31
|
const routeSet = new Set(routeStageIds);
|
|
16
32
|
const parents = new Set();
|
|
17
33
|
const snapshot = asDagSnapshot(dag);
|
|
18
34
|
for (const stageId of routeStageIds) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (child?.clonable === true) {
|
|
22
|
-
parents.add(stageId);
|
|
23
|
-
}
|
|
35
|
+
if (isCloneEmitter(nodeById(dag, stageId))) {
|
|
36
|
+
parents.add(stageId);
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
39
|
for (const stageId of routeStageIds) {
|
|
27
|
-
const node = dag
|
|
28
|
-
if (node
|
|
40
|
+
const node = nodeById(dag, stageId);
|
|
41
|
+
if (node !== undefined && parentIsCloneEmitter(dag, node) && node.needs !== null) {
|
|
29
42
|
parents.add(node.needs);
|
|
30
43
|
}
|
|
31
44
|
}
|
|
@@ -38,24 +51,25 @@ export function routeRelatedForkParentIds(dag, routeStageIds) {
|
|
|
38
51
|
}
|
|
39
52
|
}
|
|
40
53
|
for (const stageId of routeStageIds) {
|
|
41
|
-
const node = dag
|
|
42
|
-
if (node
|
|
54
|
+
const node = nodeById(dag, stageId);
|
|
55
|
+
if (!parentIsCloneEmitter(dag, node) || isMintedCloneInstance(node))
|
|
43
56
|
continue;
|
|
44
57
|
const instances = instancesOfDefinition(snapshot, stageId).filter((id) => id !== stageId);
|
|
45
|
-
if (instances.length > 0 && node.needs !== null) {
|
|
58
|
+
if (instances.length > 0 && node !== undefined && node.needs !== null) {
|
|
46
59
|
parents.add(node.needs);
|
|
47
60
|
}
|
|
48
61
|
}
|
|
49
62
|
return [...parents];
|
|
50
63
|
}
|
|
51
|
-
/** Clone instance ids currently under
|
|
64
|
+
/** Clone instance ids currently under Clone Chain children of a fork parent. */
|
|
52
65
|
export function cloneInstanceIdsForForkParent(dag, forkParentStageId) {
|
|
53
66
|
const snapshot = asDagSnapshot(dag);
|
|
54
67
|
const ids = [];
|
|
55
68
|
const seen = new Set();
|
|
69
|
+
const parentIsEmitter = isCloneEmitter(nodeById(dag, forkParentStageId));
|
|
56
70
|
for (const childId of dag.childrenOf[forkParentStageId] ?? []) {
|
|
57
|
-
const child = dag
|
|
58
|
-
if (
|
|
71
|
+
const child = nodeById(dag, childId);
|
|
72
|
+
if (parentIsEmitter && !isMintedCloneInstance(child)) {
|
|
59
73
|
for (const id of instancesOfDefinition(snapshot, childId)) {
|
|
60
74
|
if (id === childId)
|
|
61
75
|
continue;
|
|
@@ -237,6 +251,22 @@ export async function selectActiveInput(options) {
|
|
|
237
251
|
}
|
|
238
252
|
return activeCohortForParent(options.store, options.runId, forkParentId);
|
|
239
253
|
}
|
|
254
|
+
export async function forkCohortMapsFromStore(store, runId) {
|
|
255
|
+
const gens = await store.listForkGenerations(runId);
|
|
256
|
+
const supersededCloneIds = new Set();
|
|
257
|
+
const cohortByForkParent = new Map();
|
|
258
|
+
for (const gen of gens) {
|
|
259
|
+
if (gen.status === "superseded") {
|
|
260
|
+
for (const id of gen.clone_stage_ids) {
|
|
261
|
+
supersededCloneIds.add(id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (gen.status === "active") {
|
|
265
|
+
cohortByForkParent.set(gen.fork_parent_stage_id, new Set(gen.clone_stage_ids));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { supersededCloneIds, cohortByForkParent };
|
|
269
|
+
}
|
|
240
270
|
export function filterJoinInputs(allInstanceIds, cohort) {
|
|
241
271
|
if (cohort.kind === "untracked")
|
|
242
272
|
return [...allInstanceIds];
|
|
@@ -264,7 +294,7 @@ export function forkChoicePreservesReplaySource(dag, chosen, sourceStageId) {
|
|
|
264
294
|
return true;
|
|
265
295
|
}
|
|
266
296
|
const child = dag.nodes.find((n) => n.id === childId);
|
|
267
|
-
if (child
|
|
297
|
+
if (parentIsCloneEmitter(dag, child) && !isMintedCloneInstance(child)) {
|
|
268
298
|
const snapshot = asDagSnapshot(dag);
|
|
269
299
|
for (const instanceId of instancesOfDefinition(snapshot, childId)) {
|
|
270
300
|
if (instanceId === childId)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
2
|
+
import type { PipelineNeedEdge, ResolvedPipelineDag, ResolvedPipelineStageNode } from "../types/pipeline.js";
|
|
3
|
+
import type { StageScheduleState } from "./pipelineScheduler.js";
|
|
4
|
+
import { type RouteIfEval } from "./routeIfEval.js";
|
|
5
|
+
export declare function expandJoinParent(dag: ResolvedPipelineDag, catalogParentId: string): string[];
|
|
6
|
+
export declare function inboundEdgeFired(edge: PipelineNeedEdge, parentId: string, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>): boolean;
|
|
7
|
+
export declare function joinAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>): boolean;
|
|
8
|
+
export type InboundAfterSuccess = RouteIfEval | "ungated";
|
|
9
|
+
export declare function classifyInboundAfterSuccess(node: Pick<ResolvedPipelineStageNode, "needs" | "needsEdges">, parentId: string, payload: Record<string, unknown> | undefined): InboundAfterSuccess;
|
|
10
|
+
export declare function isEagerSingleParentIfSkip(node: Pick<ResolvedPipelineStageNode, "needs" | "needsEdges">, parentId: string, payload: Record<string, unknown> | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Finds pending multi-parent joins that cannot run once every parent is
|
|
13
|
+
* terminal: all-skipped, or all-succeeded with an inbound `if` miss.
|
|
14
|
+
* A failed parent is not a stalled skip — that join stays pending.
|
|
15
|
+
*/
|
|
16
|
+
export declare function pickStalledJoinSkips(dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, completedEnvelopes?: Map<string, StageEnvelope>): string[];
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { isNeedTerminalState, predecessorEdges } from "../config/pipelineNeeds.js";
|
|
2
|
+
import { instancesOfDefinition } from "../runstore/pipelineDagSnapshot.js";
|
|
3
|
+
import { evaluateRouteIf } from "./routeIfEval.js";
|
|
4
|
+
function definitionInstances(dag, catalogId) {
|
|
5
|
+
const snapshot = dag;
|
|
6
|
+
if (Array.isArray(snapshot.stage_ids)) {
|
|
7
|
+
return instancesOfDefinition(snapshot, catalogId);
|
|
8
|
+
}
|
|
9
|
+
return dag.nodes.some((n) => n.id === catalogId) ? [catalogId] : [];
|
|
10
|
+
}
|
|
11
|
+
export function expandJoinParent(dag, catalogParentId) {
|
|
12
|
+
const minted = definitionInstances(dag, catalogParentId).filter((id) => id !== catalogParentId);
|
|
13
|
+
return minted.length > 0 ? minted : [catalogParentId];
|
|
14
|
+
}
|
|
15
|
+
function expandJoinParents(dag, edges) {
|
|
16
|
+
const ids = [];
|
|
17
|
+
for (const edge of edges) {
|
|
18
|
+
ids.push(...expandJoinParent(dag, edge.id));
|
|
19
|
+
}
|
|
20
|
+
return ids;
|
|
21
|
+
}
|
|
22
|
+
function joinParentDisposition(dag, edges, states) {
|
|
23
|
+
const instances = expandJoinParents(dag, edges);
|
|
24
|
+
if (instances.length === 0)
|
|
25
|
+
return undefined;
|
|
26
|
+
let hasFailed = false;
|
|
27
|
+
let hasSucceeded = false;
|
|
28
|
+
let hasSkipped = false;
|
|
29
|
+
for (const id of instances) {
|
|
30
|
+
const state = states.get(id);
|
|
31
|
+
if (!isNeedTerminalState(state)) {
|
|
32
|
+
return { allTerminal: false };
|
|
33
|
+
}
|
|
34
|
+
if (state === "failed")
|
|
35
|
+
hasFailed = true;
|
|
36
|
+
if (state === "succeeded")
|
|
37
|
+
hasSucceeded = true;
|
|
38
|
+
if (state === "skipped")
|
|
39
|
+
hasSkipped = true;
|
|
40
|
+
}
|
|
41
|
+
return { allTerminal: true, hasFailed, hasSucceeded, hasSkipped };
|
|
42
|
+
}
|
|
43
|
+
export function inboundEdgeFired(edge, parentId, states, envelopes) {
|
|
44
|
+
if (edge.if === undefined)
|
|
45
|
+
return true;
|
|
46
|
+
if (states.get(parentId) !== "succeeded")
|
|
47
|
+
return false;
|
|
48
|
+
return evaluateRouteIf(edge.if, envelopes.get(parentId)?.payload) === "fire";
|
|
49
|
+
}
|
|
50
|
+
function inboundEdgeMissed(edge, parentId, envelopes) {
|
|
51
|
+
if (edge.if === undefined)
|
|
52
|
+
return false;
|
|
53
|
+
return evaluateRouteIf(edge.if, envelopes.get(parentId)?.payload) === "miss";
|
|
54
|
+
}
|
|
55
|
+
function everySucceededInboundEdgeFired(dag, edges, states, envelopes) {
|
|
56
|
+
for (const edge of edges) {
|
|
57
|
+
for (const id of expandJoinParent(dag, edge.id)) {
|
|
58
|
+
if (!inboundEdgeFired(edge, id, states, envelopes))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function anySucceededInboundEdgeMissed(dag, edges, envelopes) {
|
|
65
|
+
for (const edge of edges) {
|
|
66
|
+
for (const id of expandJoinParent(dag, edge.id)) {
|
|
67
|
+
if (inboundEdgeMissed(edge, id, envelopes))
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function multiParentJoinAllowsRun(dag, node, states, envelopes) {
|
|
74
|
+
const edges = predecessorEdges(node);
|
|
75
|
+
if (expandJoinParents(dag, edges).length < 2)
|
|
76
|
+
return false;
|
|
77
|
+
const disposition = joinParentDisposition(dag, edges, states);
|
|
78
|
+
if (disposition === undefined || !disposition.allTerminal)
|
|
79
|
+
return false;
|
|
80
|
+
if (disposition.hasFailed)
|
|
81
|
+
return false;
|
|
82
|
+
if (!disposition.hasSucceeded)
|
|
83
|
+
return false;
|
|
84
|
+
if (!disposition.hasSkipped) {
|
|
85
|
+
return everySucceededInboundEdgeFired(dag, edges, states, envelopes);
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A multi-parent join whose parents are all terminal, none failed, and
|
|
91
|
+
* either every parent skipped, or every parent succeeded but an inbound
|
|
92
|
+
* `if` missed. Those joins cannot run, so the scheduler force-skips them.
|
|
93
|
+
* A failed parent is not stalled: the join stays pending. A still-running
|
|
94
|
+
* parent is not stalled either.
|
|
95
|
+
*/
|
|
96
|
+
function isStalledMultiParentJoin(dag, node, states, envelopes) {
|
|
97
|
+
const edges = predecessorEdges(node);
|
|
98
|
+
if (edges.length <= 1)
|
|
99
|
+
return false;
|
|
100
|
+
const disposition = joinParentDisposition(dag, edges, states);
|
|
101
|
+
if (disposition === undefined || !disposition.allTerminal)
|
|
102
|
+
return false;
|
|
103
|
+
if (disposition.hasFailed)
|
|
104
|
+
return false;
|
|
105
|
+
if (!disposition.hasSucceeded)
|
|
106
|
+
return true;
|
|
107
|
+
if (disposition.hasSkipped)
|
|
108
|
+
return false;
|
|
109
|
+
return anySucceededInboundEdgeMissed(dag, edges, envelopes);
|
|
110
|
+
}
|
|
111
|
+
export function joinAllowsRun(dag, stageId, states, envelopes) {
|
|
112
|
+
const node = dag.nodes.find((n) => n.id === stageId);
|
|
113
|
+
if (!node)
|
|
114
|
+
return false;
|
|
115
|
+
const edges = predecessorEdges(node);
|
|
116
|
+
if (edges.length === 0)
|
|
117
|
+
return true;
|
|
118
|
+
if (expandJoinParents(dag, edges).length >= 2) {
|
|
119
|
+
return multiParentJoinAllowsRun(dag, node, states, envelopes);
|
|
120
|
+
}
|
|
121
|
+
const edge = edges[0];
|
|
122
|
+
const parentNeedId = typeof node.needs === "string" && node.needs ? node.needs : edge.id;
|
|
123
|
+
const parentId = expandJoinParent(dag, parentNeedId)[0];
|
|
124
|
+
if (states.get(parentId) !== "succeeded")
|
|
125
|
+
return false;
|
|
126
|
+
if (edge.if === undefined)
|
|
127
|
+
return true;
|
|
128
|
+
return inboundEdgeFired(edge, parentId, states, envelopes);
|
|
129
|
+
}
|
|
130
|
+
export function classifyInboundAfterSuccess(node, parentId, payload) {
|
|
131
|
+
const inbound = predecessorEdges(node).find((edge) => edge.id === parentId);
|
|
132
|
+
if (inbound?.if === undefined)
|
|
133
|
+
return "ungated";
|
|
134
|
+
return evaluateRouteIf(inbound.if, payload);
|
|
135
|
+
}
|
|
136
|
+
export function isEagerSingleParentIfSkip(node, parentId, payload) {
|
|
137
|
+
if (predecessorEdges(node).length !== 1)
|
|
138
|
+
return false;
|
|
139
|
+
return classifyInboundAfterSuccess(node, parentId, payload) === "miss";
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Finds pending multi-parent joins that cannot run once every parent is
|
|
143
|
+
* terminal: all-skipped, or all-succeeded with an inbound `if` miss.
|
|
144
|
+
* A failed parent is not a stalled skip — that join stays pending.
|
|
145
|
+
*/
|
|
146
|
+
export function pickStalledJoinSkips(dag, states, completedEnvelopes = new Map()) {
|
|
147
|
+
const ids = [];
|
|
148
|
+
for (const node of dag.nodes) {
|
|
149
|
+
if (states.get(node.id) !== "pending")
|
|
150
|
+
continue;
|
|
151
|
+
if (isStalledMultiParentJoin(dag, node, states, completedEnvelopes)) {
|
|
152
|
+
ids.push(node.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return ids;
|
|
156
|
+
}
|
|
@@ -3,7 +3,6 @@ import type { RunPipelineDagSnapshot, RunStore, StageSnapshot } from "../runstor
|
|
|
3
3
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
4
4
|
import type { LoadedPipeline, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
5
5
|
import type { TaskFile } from "../types/task.js";
|
|
6
|
-
import { applyCloneForksFromEnvelopes, applyCloneForksToSchedule } from "./cloneSchedule.js";
|
|
7
6
|
import type { FeedbackLoopDecisionInput, ResolveFeedbackLoopDecisionResult } from "./feedbackLoopDecision.js";
|
|
8
7
|
import type { PipelineRunResult } from "./pipelineRunner.js";
|
|
9
8
|
import type { StageProcessLauncher } from "./stageProcessLauncher.js";
|
|
@@ -49,8 +48,6 @@ export type HydratedSchedule = {
|
|
|
49
48
|
firstFailureReason: string | undefined;
|
|
50
49
|
dag?: RunPipelineDagSnapshot;
|
|
51
50
|
};
|
|
52
|
-
export { applyCloneForksFromEnvelopes, applyCloneForksToSchedule, };
|
|
53
|
-
export type { ApplyCloneForksResult } from "./cloneSchedule.js";
|
|
54
51
|
export declare function snapshotToScheduleState(snap: {
|
|
55
52
|
status: StageSnapshot["status"];
|
|
56
53
|
}, overrides: ReadonlyMap<string, StageScheduleState>, stageId: string, executionMode?: StageExecutionMode): StageScheduleState;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { normalizeForkChoice } from "../envelope/forkChoice.js";
|
|
2
2
|
import { buildPipelineDagSnapshotFromLoaded } from "../runstore/pipelineDagSnapshot.js";
|
|
3
3
|
import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
|
|
4
|
-
import {
|
|
4
|
+
import { cloneFailureContinuesSchedule, cloneRetryDownstream, cloneFailFastSkipIds, cloneScheduleAllowsRun, failureIsAccepted, isCloneInstance, mint, protectedClonableChildIds, sequentialLaterCloneIds, skipRejectedNeedDependents, } from "./cloneSchedule.js";
|
|
5
5
|
import { buildCompletedEnvelopesFromRun, buildStageConfigById, } from "./envelopeRouting.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { classifyInboundAfterSuccess, isEagerSingleParentIfSkip, joinAllowsRun, pickStalledJoinSkips, } from "./joinReadiness.js";
|
|
7
|
+
import { reopenRunnableSkippedStages, persistReopenedStages } from "./reopenSkipped.js";
|
|
8
|
+
import { forkChoicePreservesReplaySource, forkCohortMapsFromStore, mintCohortForFanout, } from "./forkGeneration.js";
|
|
9
|
+
import { activeReplayId, activeSourceStageId, applySendBack, createReplaySchedule, hydrateFromStore, isHeld, launchAttemptFor, launchFor, noteActiveCohort, onContinued, onRouteStageFailed, onRouteStageRunning, onRouteStageSucceeded, rebindRouteAttempts, resolveDecision, } from "./replayLifecycle.js";
|
|
8
10
|
import { WAIT_WITHOUT_WORKER_DISPATCH } from "./answerResume.js";
|
|
9
11
|
import { runStage, isRunStageWaiting } from "./stageRunner.js";
|
|
10
12
|
import { attemptContext, resumeSessionFilePath } from "./stageAttemptContext.js";
|
|
11
|
-
const EMPTY_RETRY_DOWNSTREAM = new Set();
|
|
12
13
|
function asDagSnapshot(dag) {
|
|
13
14
|
const snapshot = dag;
|
|
14
15
|
if (Array.isArray(snapshot.stage_ids))
|
|
@@ -18,7 +19,6 @@ function asDagSnapshot(dag) {
|
|
|
18
19
|
stage_ids: dag.nodes.map((n) => n.id),
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
export { applyCloneForksFromEnvelopes, applyCloneForksToSchedule, };
|
|
22
22
|
function synthesizedFailureEnvelope(reason) {
|
|
23
23
|
return { status: "failure", summary: reason, artifacts: [] };
|
|
24
24
|
}
|
|
@@ -66,6 +66,7 @@ export async function hydrateScheduleFromStore(store, runId, dag, executionMode
|
|
|
66
66
|
continue;
|
|
67
67
|
schedulingHalted = true;
|
|
68
68
|
}
|
|
69
|
+
await reopenRunnableSkippedStages(store, runId, resolvedDag, states, completedEnvelopes);
|
|
69
70
|
return {
|
|
70
71
|
states,
|
|
71
72
|
completedEnvelopes,
|
|
@@ -86,15 +87,20 @@ export async function hydrateScheduleForRetryRoots(store, runId, dag, retryStage
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
const retryRootSet = new Set(retryStageIds);
|
|
90
|
+
const { supersededCloneIds } = await forkCohortMapsFromStore(store, runId);
|
|
89
91
|
const states = new Map();
|
|
90
92
|
for (const node of resolvedDag.nodes) {
|
|
91
93
|
states.set(node.id, "pending");
|
|
92
94
|
}
|
|
93
95
|
const overrides = new Map();
|
|
94
96
|
for (const rootId of retryStageIds) {
|
|
97
|
+
if (supersededCloneIds.has(rootId))
|
|
98
|
+
continue;
|
|
95
99
|
overrides.set(rootId, "pending");
|
|
96
100
|
}
|
|
97
101
|
for (const id of downstream) {
|
|
102
|
+
if (supersededCloneIds.has(id))
|
|
103
|
+
continue;
|
|
98
104
|
overrides.set(id, "pending");
|
|
99
105
|
}
|
|
100
106
|
for (const snap of runDetail.stages) {
|
|
@@ -105,6 +111,20 @@ export async function hydrateScheduleForRetryRoots(store, runId, dag, retryStage
|
|
|
105
111
|
completedEnvelopes.delete(id);
|
|
106
112
|
}
|
|
107
113
|
}
|
|
114
|
+
const skippedToReopen = [];
|
|
115
|
+
const skipStates = new Map();
|
|
116
|
+
for (const snap of runDetail.stages) {
|
|
117
|
+
const id = snap.stage_id;
|
|
118
|
+
if (supersededCloneIds.has(id))
|
|
119
|
+
continue;
|
|
120
|
+
if (snap.status !== "skipped")
|
|
121
|
+
continue;
|
|
122
|
+
if (!retryRootSet.has(id) && !downstream.has(id))
|
|
123
|
+
continue;
|
|
124
|
+
skippedToReopen.push(id);
|
|
125
|
+
skipStates.set(id, "skipped");
|
|
126
|
+
}
|
|
127
|
+
await persistReopenedStages(store, runId, skippedToReopen, skipStates);
|
|
108
128
|
return {
|
|
109
129
|
states,
|
|
110
130
|
completedEnvelopes,
|
|
@@ -284,21 +304,9 @@ function isRunnable(dag, stageId, states, schedulingHalted, completedEnvelopes,
|
|
|
284
304
|
const state = states.get(stageId);
|
|
285
305
|
if (state !== "pending")
|
|
286
306
|
return false;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (override === undefined || override.size === 0) {
|
|
291
|
-
return { kind: "untracked" };
|
|
292
|
-
}
|
|
293
|
-
const forkParentId = forkParentForNeedsStage(dag, needsId);
|
|
294
|
-
if (forkParentId === null)
|
|
295
|
-
return { kind: "untracked" };
|
|
296
|
-
const tracked = override.get(forkParentId);
|
|
297
|
-
if (tracked === undefined)
|
|
298
|
-
return { kind: "untracked" };
|
|
299
|
-
return activeCohortFromCloneIds(tracked);
|
|
300
|
-
},
|
|
301
|
-
});
|
|
307
|
+
if (!joinAllowsRun(dag, stageId, states, completedEnvelopes))
|
|
308
|
+
return false;
|
|
309
|
+
return cloneScheduleAllowsRun(dag, stageId, states, completedEnvelopes);
|
|
302
310
|
}
|
|
303
311
|
export function hydratedScheduleHasRunnableWork(hydrated) {
|
|
304
312
|
if (hydrated.schedulingHalted)
|
|
@@ -403,13 +411,6 @@ export async function runPipelineDag(options) {
|
|
|
403
411
|
}
|
|
404
412
|
}
|
|
405
413
|
applyForkSkipsFromEnvelopes(dag, states, completedEnvelopes);
|
|
406
|
-
{
|
|
407
|
-
const applied = applyCloneForksFromEnvelopes(dag, states, completedEnvelopes);
|
|
408
|
-
if (applied.dag !== dag) {
|
|
409
|
-
await store.updatePipelineDag(run.runId, applied.dag);
|
|
410
|
-
}
|
|
411
|
-
dag = applied.dag;
|
|
412
|
-
}
|
|
413
414
|
let activeCount = 0;
|
|
414
415
|
const inFlight = new Set();
|
|
415
416
|
const launchedIds = new Set();
|
|
@@ -442,6 +443,28 @@ export async function runPipelineDag(options) {
|
|
|
442
443
|
await store.appendStageEvent(run.runId, id, { event: "skipped" });
|
|
443
444
|
await persistSkipRejected(id, "skipped");
|
|
444
445
|
};
|
|
446
|
+
/**
|
|
447
|
+
* Finalizes multi-parent (implicit-AND) join nodes that are permanently
|
|
448
|
+
* stuck in `pending`: every predecessor edge has reached a terminal state,
|
|
449
|
+
* but `joinAllowsRun` still can't be satisfied, and terminal
|
|
450
|
+
* states never change back. `shouldSkipForObservedNeed` deliberately
|
|
451
|
+
* refuses to decide these nodes from a single resolving parent (see its
|
|
452
|
+
* comment in cloneSchedule.ts), so this is the only place their fate gets
|
|
453
|
+
* sealed -- once we're sure every sibling has actually settled. Runs once
|
|
454
|
+
* per scheduler tick; loops until a pass finds nothing new, so a chain of
|
|
455
|
+
* stalled joins settles within a single tick rather than one per ~25ms
|
|
456
|
+
* poll.
|
|
457
|
+
*/
|
|
458
|
+
const applyStalledJoinSkips = async () => {
|
|
459
|
+
for (;;) {
|
|
460
|
+
const ids = pickStalledJoinSkips(dag, states, completedEnvelopes);
|
|
461
|
+
if (ids.length === 0)
|
|
462
|
+
return;
|
|
463
|
+
for (const id of ids) {
|
|
464
|
+
await persistSkipPending(id);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
445
468
|
for (const [stageId, state] of states) {
|
|
446
469
|
if (state !== "failed")
|
|
447
470
|
continue;
|
|
@@ -569,22 +592,33 @@ export async function runPipelineDag(options) {
|
|
|
569
592
|
}
|
|
570
593
|
};
|
|
571
594
|
const onStageSuccess = async (stageId, envelope) => {
|
|
572
|
-
const retryDownstreamIds = retryContext
|
|
573
|
-
? cloneRetryDownstream(dag, [...retryContext.retryRoots.keys()])
|
|
574
|
-
: EMPTY_RETRY_DOWNSTREAM;
|
|
575
|
-
const forceFreshCloneIds = activeReplayId(feedbackSchedule) !== undefined;
|
|
576
|
-
const conflict = cloneFanoutConflict(dag, envelope, states, retryDownstreamIds, { forceFreshCloneIds });
|
|
577
|
-
if (conflict !== undefined) {
|
|
578
|
-
await store.appendStageEvent(run.runId, stageId, {
|
|
579
|
-
event: "failed",
|
|
580
|
-
reason: conflict,
|
|
581
|
-
});
|
|
582
|
-
await onStageFailure(stageId, conflict);
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
595
|
states.set(stageId, "succeeded");
|
|
586
596
|
completedEnvelopes.set(stageId, envelope);
|
|
587
597
|
notifyRetryRootTerminal(stageId, "succeeded");
|
|
598
|
+
const minted = mint(dag, stageId, envelope);
|
|
599
|
+
if (minted.kind === "error") {
|
|
600
|
+
await handlePostSuccessError(stageId, minted.reason);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (minted.kind === "minted") {
|
|
604
|
+
dag = minted.dag;
|
|
605
|
+
await store.updatePipelineDag(run.runId, dag);
|
|
606
|
+
states.delete(minted.catalogChildId);
|
|
607
|
+
for (const id of minted.instanceIds) {
|
|
608
|
+
states.set(id, "pending");
|
|
609
|
+
}
|
|
610
|
+
const replayId = activeReplayId(feedbackSchedule);
|
|
611
|
+
if (replayId !== undefined) {
|
|
612
|
+
noteActiveCohort(feedbackSchedule, stageId, minted.instanceIds);
|
|
613
|
+
await mintCohortForFanout({
|
|
614
|
+
store,
|
|
615
|
+
runId: run.runId,
|
|
616
|
+
replayId,
|
|
617
|
+
forkParent: stageId,
|
|
618
|
+
cloneStageIds: minted.instanceIds,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
588
622
|
const feedbackAction = envelope.feedback_loop;
|
|
589
623
|
if (feedbackAction?.action === "send_back") {
|
|
590
624
|
const node = dag.nodes.find((n) => n.id === stageId);
|
|
@@ -699,6 +733,18 @@ export async function runPipelineDag(options) {
|
|
|
699
733
|
return;
|
|
700
734
|
}
|
|
701
735
|
const skippedIds = [];
|
|
736
|
+
// applyForkChoiceToSchedule (and everything it calls, including
|
|
737
|
+
// skipRejectedNeedDependents in cloneSchedule.ts) is fully synchronous
|
|
738
|
+
// -- no `await` anywhere in that call chain. So by the time this call
|
|
739
|
+
// returns, `states` already reflects every one of this stage's
|
|
740
|
+
// unchosen children as terminal, atomically, before the persistence
|
|
741
|
+
// loop below runs its first `await`. A downstream multi-parent join
|
|
742
|
+
// (see pickStalledJoinSkips) that depends on several of these children
|
|
743
|
+
// therefore always decides off fully-consistent in-memory state, even
|
|
744
|
+
// though its own `stage_events` row can end up persisted *between*
|
|
745
|
+
// two of these sequential appendStageEvent calls below (this loop) --
|
|
746
|
+
// that interleaving is only a SQLite write-ordering artifact, not a
|
|
747
|
+
// sign the join decided early.
|
|
702
748
|
applyForkChoiceToSchedule(dag, stageId, chosen, states, {
|
|
703
749
|
protectedIds,
|
|
704
750
|
onSkip: (id) => {
|
|
@@ -710,33 +756,24 @@ export async function runPipelineDag(options) {
|
|
|
710
756
|
await store.appendStageEvent(run.runId, id, { event: "skipped" });
|
|
711
757
|
}
|
|
712
758
|
}
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
continue;
|
|
729
|
-
const replayId = activeReplayId(feedbackSchedule);
|
|
730
|
-
await mintCohortForFanout({
|
|
731
|
-
store,
|
|
732
|
-
runId: run.runId,
|
|
733
|
-
...(replayId !== undefined ? { replayId } : {}),
|
|
734
|
-
forkParent: stageId,
|
|
735
|
-
cloneStageIds,
|
|
736
|
-
});
|
|
737
|
-
noteActiveCohort(feedbackSchedule, stageId, cloneStageIds);
|
|
759
|
+
const childIds = dag.childrenOf[stageId] ?? [];
|
|
760
|
+
for (const childId of childIds) {
|
|
761
|
+
const child = dag.nodes.find((node) => node.id === childId);
|
|
762
|
+
if (!child)
|
|
763
|
+
continue;
|
|
764
|
+
const result = classifyInboundAfterSuccess(child, stageId, envelope.payload);
|
|
765
|
+
if (result === "missing_field") {
|
|
766
|
+
schedulingHalted = true;
|
|
767
|
+
if (firstFailureReason === undefined) {
|
|
768
|
+
firstFailureReason = `stage "${stageId}": route if field missing from payload`;
|
|
769
|
+
}
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (isEagerSingleParentIfSkip(child, stageId, envelope.payload)) {
|
|
773
|
+
await persistSkipPending(childId);
|
|
738
774
|
}
|
|
739
775
|
}
|
|
776
|
+
await reopenRunnableSkippedStages(store, run.runId, dag, states, completedEnvelopes);
|
|
740
777
|
};
|
|
741
778
|
const handlePostSuccessError = async (stageId, reason) => {
|
|
742
779
|
if (states.get(stageId) === "succeeded") {
|
|
@@ -891,6 +928,7 @@ export async function runPipelineDag(options) {
|
|
|
891
928
|
};
|
|
892
929
|
while (!allTerminal()) {
|
|
893
930
|
drainRetryMutations();
|
|
931
|
+
await applyStalledJoinSkips();
|
|
894
932
|
if (options.onLoopTick !== undefined) {
|
|
895
933
|
await options.onLoopTick();
|
|
896
934
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
|
+
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
4
|
+
import type { StageScheduleState } from "./pipelineScheduler.js";
|
|
5
|
+
export declare function isUnchosenForkChild(dag: ResolvedPipelineDag, stageId: string, envelopes: Map<string, StageEnvelope>): boolean;
|
|
6
|
+
export declare function eventsShowStarted(events: ReadonlyArray<{
|
|
7
|
+
event: string;
|
|
8
|
+
}>): boolean;
|
|
9
|
+
export declare function shouldReopenSkippedStage(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>, hasStarted: boolean, options?: {
|
|
10
|
+
supersededCloneIds?: ReadonlySet<string>;
|
|
11
|
+
}): boolean;
|
|
12
|
+
export declare function persistReopenedStages(store: Pick<RunStore, "appendStageEvent">, runId: string, stageIds: Iterable<string>, states: Map<string, StageScheduleState>): Promise<void>;
|
|
13
|
+
export declare function reopenRunnableSkippedStages(store: Pick<RunStore, "appendStageEvent" | "listStageEvents" | "listForkGenerations">, runId: string, dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { normalizeForkChoice } from "../envelope/forkChoice.js";
|
|
2
|
+
import { forkCohortMapsFromStore } from "./forkGeneration.js";
|
|
3
|
+
import { joinAllowsRun } from "./joinReadiness.js";
|
|
4
|
+
export function isUnchosenForkChild(dag, stageId, envelopes) {
|
|
5
|
+
for (const node of dag.nodes) {
|
|
6
|
+
const envelope = envelopes.get(node.id);
|
|
7
|
+
if (envelope?.fork_choice === undefined || envelope.fork_choice === null) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const children = dag.childrenOf[node.id] ?? [];
|
|
11
|
+
if (!children.includes(stageId))
|
|
12
|
+
continue;
|
|
13
|
+
const chosen = normalizeForkChoice(envelope.fork_choice, "stored");
|
|
14
|
+
if (!chosen.has(stageId))
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
export function eventsShowStarted(events) {
|
|
20
|
+
return events.some((event) => event.event === "started" || event.event === "resumed");
|
|
21
|
+
}
|
|
22
|
+
export function shouldReopenSkippedStage(dag, stageId, states, envelopes, hasStarted, options) {
|
|
23
|
+
if (hasStarted)
|
|
24
|
+
return false;
|
|
25
|
+
if (options?.supersededCloneIds?.has(stageId))
|
|
26
|
+
return false;
|
|
27
|
+
if (states.get(stageId) !== "skipped")
|
|
28
|
+
return false;
|
|
29
|
+
if (isUnchosenForkChild(dag, stageId, envelopes))
|
|
30
|
+
return false;
|
|
31
|
+
const hypothetical = new Map(states);
|
|
32
|
+
hypothetical.set(stageId, "pending");
|
|
33
|
+
return joinAllowsRun(dag, stageId, hypothetical, envelopes);
|
|
34
|
+
}
|
|
35
|
+
export async function persistReopenedStages(store, runId, stageIds, states) {
|
|
36
|
+
for (const id of stageIds) {
|
|
37
|
+
if (states.get(id) !== "skipped")
|
|
38
|
+
continue;
|
|
39
|
+
await store.appendStageEvent(runId, id, { event: "reopened" });
|
|
40
|
+
states.set(id, "pending");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function reopenRunnableSkippedStages(store, runId, dag, states, envelopes) {
|
|
44
|
+
const { supersededCloneIds } = await forkCohortMapsFromStore(store, runId);
|
|
45
|
+
const ids = [];
|
|
46
|
+
for (const [id, state] of states) {
|
|
47
|
+
if (state !== "skipped")
|
|
48
|
+
continue;
|
|
49
|
+
const events = await store.listStageEvents(runId, id);
|
|
50
|
+
if (!shouldReopenSkippedStage(dag, id, states, envelopes, eventsShowStarted(events), { supersededCloneIds })) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
ids.push(id);
|
|
54
|
+
}
|
|
55
|
+
await persistReopenedStages(store, runId, ids, states);
|
|
56
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentPort } from "../agent/port.js";
|
|
2
|
+
import type { RunStore } from "../runstore/port.js";
|
|
3
|
+
import type { StageExecutionMode } from "./stageConcurrency.js";
|
|
4
|
+
import type { StageProcessLauncher } from "./stageProcessLauncher.js";
|
|
5
|
+
import { StageHitlController } from "./stageHitl.js";
|
|
6
|
+
import { type OperatorCatalog } from "./stageAttemptBootstrap.js";
|
|
7
|
+
export type ResumeTimedOutContext = {
|
|
8
|
+
runId: string;
|
|
9
|
+
stageId: string;
|
|
10
|
+
agent: AgentPort;
|
|
11
|
+
store: RunStore;
|
|
12
|
+
hitl: StageHitlController;
|
|
13
|
+
executionMode: StageExecutionMode;
|
|
14
|
+
stageProcessLauncher?: StageProcessLauncher;
|
|
15
|
+
cwd: string;
|
|
16
|
+
factoryCwd?: string;
|
|
17
|
+
maxActiveStagesPerRun: number;
|
|
18
|
+
operatorCatalog?: OperatorCatalog;
|
|
19
|
+
};
|
|
20
|
+
export declare function assertTimedOutStageEligible(status: string, events: ReadonlyArray<{
|
|
21
|
+
event: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}>): {
|
|
24
|
+
ok: true;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: string;
|
|
28
|
+
status: 409;
|
|
29
|
+
};
|
|
30
|
+
export declare function reconstructTimedOutAndContinue(ctx: ResumeTimedOutContext): Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
reason?: string;
|
|
33
|
+
}>;
|