stageflow 0.9.0 → 0.13.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 +6 -1
- package/dist/agent/activity.d.ts +9 -0
- package/dist/agent/agentBackend.d.ts +32 -0
- package/dist/agent/agentBackend.js +38 -0
- package/dist/agent/claudeActivity.d.ts +15 -0
- package/dist/agent/claudeActivity.js +58 -0
- package/dist/agent/claudeAdapter.d.ts +5 -0
- package/dist/agent/claudeAdapter.js +331 -0
- package/dist/agent/claudeSession.d.ts +18 -0
- package/dist/agent/claudeSession.js +54 -0
- package/dist/agent/claudeTools.d.ts +89 -0
- package/dist/agent/claudeTools.js +135 -0
- package/dist/agent/fakeAgent.js +24 -4
- package/dist/agent/piAdapter.d.ts +12 -7
- package/dist/agent/piAdapter.js +194 -44
- package/dist/agent/piIsolatedMcp.d.ts +23 -0
- package/dist/agent/piIsolatedMcp.js +223 -0
- package/dist/agent/piIsolatedMcpProbe.d.ts +15 -0
- package/dist/agent/piIsolatedMcpProbe.js +292 -0
- package/dist/agent/port.d.ts +52 -1
- package/dist/agent/port.js +1 -1
- package/dist/agent/resolveAgentPort.d.ts +26 -0
- package/dist/agent/resolveAgentPort.js +39 -0
- package/dist/cli/runCommand.js +2 -2
- package/dist/cli/runsCommand.d.ts +1 -1
- package/dist/cli/runsCommand.js +184 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +11 -5
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +2 -1
- package/dist/config/createPipeline.js +34 -9
- package/dist/config/createStage.js +14 -9
- package/dist/config/loadPipeline.js +11 -1
- package/dist/config/loadStage.d.ts +1 -0
- package/dist/config/loadStage.js +207 -2
- package/dist/config/loadStageflowManifest.js +14 -1
- package/dist/config/mergePipelineIncludes.d.ts +1 -0
- package/dist/config/mergePipelineIncludes.js +14 -1
- package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
- package/dist/config/normalizePipelineStageEntry.js +56 -7
- package/dist/config/parseCompletionContract.d.ts +8 -0
- package/dist/config/parseCompletionContract.js +218 -0
- package/dist/config/parsePreEmitChecks.d.ts +3 -0
- package/dist/config/parsePreEmitChecks.js +65 -0
- package/dist/config/pipelineNeeds.d.ts +19 -0
- package/dist/config/pipelineNeeds.js +151 -0
- package/dist/config/pipelineStageKeys.js +17 -1
- package/dist/config/resolveForkEmitContext.d.ts +6 -2
- package/dist/config/resolveForkEmitContext.js +9 -2
- package/dist/config/resolvePipelineDag.d.ts +2 -1
- package/dist/config/resolvePipelineDag.js +182 -39
- package/dist/config/resolveStageMcpServers.d.ts +35 -0
- package/dist/config/resolveStageMcpServers.js +207 -0
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +51 -0
- package/dist/config/validateCompletionContract.d.ts +4 -0
- package/dist/config/validateCompletionContract.js +52 -0
- package/dist/envelope/check.d.ts +1 -1
- package/dist/envelope/check.js +86 -13
- package/dist/envelope/cloneForks.d.ts +1 -1
- package/dist/envelope/cloneForks.js +24 -9
- package/dist/envelope/feedbackLoop.d.ts +4 -0
- package/dist/envelope/feedbackLoop.js +31 -0
- package/dist/envelope/payloadSchema.d.ts +7 -1
- package/dist/envelope/payloadSchema.js +170 -13
- package/dist/envelope/preEmitChecks.d.ts +29 -0
- package/dist/envelope/preEmitChecks.js +57 -0
- package/dist/hitl/qaTrail.d.ts +25 -0
- package/dist/hitl/qaTrail.js +51 -0
- package/dist/mcp/catalogTools.js +4 -2
- package/dist/mcp/controlTools.js +72 -0
- package/dist/mcp/readArtifact.d.ts +2 -0
- package/dist/mcp/readArtifact.js +25 -1
- package/dist/mcp/waitingGates.d.ts +2 -1
- package/dist/mcp/waitingGates.js +17 -1
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +4 -1
- package/dist/projection/projectRun.js +7 -0
- package/dist/prompt/feedbackLoopContext.d.ts +2 -0
- package/dist/prompt/feedbackLoopContext.js +17 -0
- package/dist/prompt/priorEnvelope.d.ts +2 -2
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
- package/dist/runstore/pipelineDagSnapshot.js +51 -11
- package/dist/runstore/port.d.ts +185 -3
- package/dist/runstore/port.js +42 -3
- package/dist/runstore/runProjection.d.ts +7 -3
- package/dist/runstore/runProjection.js +30 -15
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +99 -0
- package/dist/runstore/stageInstanceId.d.ts +1 -1
- package/dist/runstore/stageInstanceId.js +3 -2
- package/dist/runstore/trackProjection.js +59 -27
- package/dist/runstore/verificationHistory.d.ts +19 -0
- package/dist/runstore/verificationHistory.js +30 -0
- package/dist/runtime/cloneSchedule.d.ts +40 -4
- package/dist/runtime/cloneSchedule.js +212 -28
- package/dist/runtime/completionCheckRunner.d.ts +137 -0
- package/dist/runtime/completionCheckRunner.js +365 -0
- package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
- package/dist/runtime/completionCheckoutBaseline.js +44 -0
- package/dist/runtime/envelopeRouting.d.ts +10 -3
- package/dist/runtime/envelopeRouting.js +146 -5
- package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
- package/dist/runtime/feedbackLoopCoordinator.js +446 -0
- package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
- package/dist/runtime/feedbackLoopDecision.js +207 -0
- package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
- package/dist/runtime/feedbackLoopRoute.js +13 -0
- package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
- package/dist/runtime/feedbackLoopSchedule.js +72 -0
- package/dist/runtime/forkGeneration.d.ts +72 -0
- package/dist/runtime/forkGeneration.js +295 -0
- package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
- package/dist/runtime/gitCheckoutCapability.js +97 -0
- package/dist/runtime/manualRecoveryState.d.ts +40 -0
- package/dist/runtime/manualRecoveryState.js +73 -0
- package/dist/runtime/pipelineScheduler.d.ts +13 -1
- package/dist/runtime/pipelineScheduler.js +328 -45
- package/dist/runtime/replayLifecycle.d.ts +107 -0
- package/dist/runtime/replayLifecycle.js +168 -0
- package/dist/runtime/runManager.d.ts +54 -1
- package/dist/runtime/runManager.js +471 -6
- package/dist/runtime/runRetryCoordinator.d.ts +6 -1
- package/dist/runtime/runRetryCoordinator.js +43 -9
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
- package/dist/runtime/stageAttemptBootstrap.js +117 -3
- package/dist/runtime/stageProcessLauncher.d.ts +1 -1
- package/dist/runtime/stageRecovery.js +1 -1
- package/dist/runtime/stageRunner.d.ts +4 -1
- package/dist/runtime/stageRunner.js +29 -3
- package/dist/runtime/stageWorker.js +69 -2
- package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
- package/dist/runtime/verifiedStageExecution.d.ts +31 -0
- package/dist/runtime/verifiedStageExecution.js +127 -0
- package/dist/server/bootstrap.js +1 -0
- package/dist/server/http.js +137 -4
- package/dist/server/operatorResults.js +2 -0
- package/dist/server/projectMcpRoutes.d.ts +6 -0
- package/dist/server/projectMcpRoutes.js +38 -0
- package/dist/tools/askOperator.d.ts +161 -5
- package/dist/tools/askOperator.js +58 -25
- package/dist/tools/emitStageEnvelope.d.ts +12 -12
- package/dist/tools/emitStageEnvelope.js +112 -24
- package/dist/types/completion.d.ts +43 -0
- package/dist/types/completion.js +1 -0
- package/dist/types/envelope.d.ts +18 -0
- package/dist/types/forkChoice.d.ts +9 -4
- package/dist/types/forkChoice.js +1 -1
- package/dist/types/pipeline.d.ts +41 -2
- package/dist/types/preEmitCheck.d.ts +18 -0
- package/dist/types/preEmitCheck.js +1 -0
- package/dist/types/stage.d.ts +14 -0
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/ui/assets/index-Yzh42vhf.js +118 -0
- package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +5 -1
- package/skills/stageflow-author/SKILL.md +20 -2
- package/skills/stageflow-author/references/catalog-mapping.md +88 -3
- package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
- package/skills/stageflow-author/references/stage-prompt-template.md +63 -4
- package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -1
- package/dist/ui/assets/index-CSoGDA3A.js +0 -118
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { isNeedTerminalState, predecessorEdges } from "../config/pipelineNeeds.js";
|
|
1
2
|
import { appendCloneInstances, instancesOfDefinition, } from "../runstore/pipelineDagSnapshot.js";
|
|
2
3
|
import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
|
|
3
|
-
import {
|
|
4
|
+
import { collectDownstreamStageIds } from "./dagTraversal.js";
|
|
5
|
+
import { activeCohortFromCloneIds, filterJoinInputs, } from "./forkGeneration.js";
|
|
4
6
|
function asDagSnapshot(dag) {
|
|
5
7
|
const snapshot = dag;
|
|
6
8
|
if (Array.isArray(snapshot.stage_ids))
|
|
@@ -86,13 +88,75 @@ export function sequentialLaterCloneIds(dag, stageId, completedEnvelopes) {
|
|
|
86
88
|
return [];
|
|
87
89
|
return instances.slice(i + 1);
|
|
88
90
|
}
|
|
91
|
+
function parentNeedEdge(node, parentId, parentDefId) {
|
|
92
|
+
return predecessorEdges(node).find((edge) => edge.id === parentId || edge.id === parentDefId);
|
|
93
|
+
}
|
|
94
|
+
function edgeInstancesReady(dag, edge, states) {
|
|
95
|
+
const instances = definitionInstances(dag, edge.id);
|
|
96
|
+
if (instances.length === 0)
|
|
97
|
+
return false;
|
|
98
|
+
const hasSucceededSibling = instances.some((id) => states.get(id) === "succeeded");
|
|
99
|
+
return instances.every((id) => {
|
|
100
|
+
const state = states.get(id);
|
|
101
|
+
if (!isNeedTerminalState(state))
|
|
102
|
+
return false;
|
|
103
|
+
if (edge.on.includes(state))
|
|
104
|
+
return true;
|
|
105
|
+
return state === "skipped" && hasSucceededSibling;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function shouldSkipForObservedNeed(dag, node, parentId, parentDefId, observed) {
|
|
109
|
+
const edge = parentNeedEdge(node, parentId, parentDefId);
|
|
110
|
+
if (!edge)
|
|
111
|
+
return false;
|
|
112
|
+
if (edge.on.includes(observed))
|
|
113
|
+
return false;
|
|
114
|
+
if (observed === "skipped" &&
|
|
115
|
+
predecessorEdges(node).length === 1 &&
|
|
116
|
+
typeof node.needs === "string" &&
|
|
117
|
+
isCloneInstance(dag, parentId)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
export function skipRejectedNeedDependents(dag, parentId, states, observed, onSkip) {
|
|
123
|
+
const parentDefId = definitionIdForInstance(asDagSnapshot(dag), parentId);
|
|
124
|
+
for (const node of dag.nodes) {
|
|
125
|
+
if (states.get(node.id) !== "pending")
|
|
126
|
+
continue;
|
|
127
|
+
if (!shouldSkipForObservedNeed(dag, node, parentId, parentDefId, observed)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
onSkip(node.id);
|
|
131
|
+
skipRejectedNeedDependents(dag, node.id, states, "skipped", onSkip);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function failureIsAccepted(dag, failedId, states) {
|
|
135
|
+
const defId = definitionIdForInstance(asDagSnapshot(dag), failedId);
|
|
136
|
+
for (const node of dag.nodes) {
|
|
137
|
+
const edge = parentNeedEdge(node, failedId, defId);
|
|
138
|
+
if (!edge?.on.includes("failed"))
|
|
139
|
+
continue;
|
|
140
|
+
const state = states.get(node.id);
|
|
141
|
+
if (state !== undefined && state !== "skipped")
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
89
146
|
function joinSuccessorIds(dag, successorId) {
|
|
90
|
-
return dag.nodes
|
|
147
|
+
return dag.nodes
|
|
148
|
+
.filter((n) => predecessorEdges(n).some((edge) => edge.id === successorId))
|
|
149
|
+
.map((n) => n.id);
|
|
91
150
|
}
|
|
92
151
|
function joinAndDownstreamIds(dag, cloneStageId) {
|
|
93
152
|
const defId = definitionIdForInstance(asDagSnapshot(dag), cloneStageId);
|
|
94
153
|
const ids = [];
|
|
95
154
|
for (const joinId of joinSuccessorIds(dag, defId)) {
|
|
155
|
+
const node = dag.nodes.find((n) => n.id === joinId);
|
|
156
|
+
if (node &&
|
|
157
|
+
!shouldSkipForObservedNeed(dag, node, cloneStageId, defId, "failed")) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
96
160
|
ids.push(joinId);
|
|
97
161
|
for (const desc of collectDownstreamStageIds(dag, joinId)) {
|
|
98
162
|
ids.push(desc);
|
|
@@ -100,13 +164,33 @@ function joinAndDownstreamIds(dag, cloneStageId) {
|
|
|
100
164
|
}
|
|
101
165
|
return ids;
|
|
102
166
|
}
|
|
103
|
-
|
|
167
|
+
/**
|
|
168
|
+
* A fresh clone_forks fanout targeting a catalog id that already has
|
|
169
|
+
* instances is only safe when every existing instance was put back to
|
|
170
|
+
* `pending` by the current retry cascade (`retryDownstreamIds`) — that means
|
|
171
|
+
* an ancestor was legitimately retried and this parent re-emitted the same
|
|
172
|
+
* fanout for reactivation. Instances that are still active/terminal, or
|
|
173
|
+
* merely `pending` without having gone through a retry reset (e.g. an
|
|
174
|
+
* envelope processed twice in one live run), are rejected as before.
|
|
175
|
+
*
|
|
176
|
+
* Feedback-loop replay is different: the prior cohort was superseded
|
|
177
|
+
* (`skipped`) and a new generation must mint fresh `~N` ids, so
|
|
178
|
+
* `forceFreshCloneIds` bypasses the conflict.
|
|
179
|
+
*/
|
|
180
|
+
export function cloneFanoutConflict(dag, envelope, states, retryDownstreamIds, options) {
|
|
181
|
+
if (options?.forceFreshCloneIds === true)
|
|
182
|
+
return undefined;
|
|
104
183
|
const snapshot = asDagSnapshot(dag);
|
|
105
184
|
for (const item of envelope.clone_forks ?? []) {
|
|
106
185
|
if (item.action !== "fanout")
|
|
107
186
|
continue;
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
const existing = definitionInstances(dag, item.successor_id).filter((id) => id !== item.successor_id);
|
|
188
|
+
if (existing.length > 0) {
|
|
189
|
+
const reactivatable = existing.every((id) => states.get(id) === "pending" && retryDownstreamIds.has(id));
|
|
190
|
+
if (!reactivatable) {
|
|
191
|
+
return `clone fan-out for "${item.successor_id}" is already instanced`;
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
110
194
|
}
|
|
111
195
|
if (!snapshot.stage_ids.includes(item.successor_id)) {
|
|
112
196
|
return `clone fan-out for "${item.successor_id}" is already instanced`;
|
|
@@ -114,6 +198,18 @@ export function cloneFanoutConflict(dag, envelope) {
|
|
|
114
198
|
}
|
|
115
199
|
return undefined;
|
|
116
200
|
}
|
|
201
|
+
export function nextFreeCloneSuffix(snapshot, catalogId) {
|
|
202
|
+
const prefix = `${catalogId}~`;
|
|
203
|
+
let max = 0;
|
|
204
|
+
for (const id of snapshot.stage_ids) {
|
|
205
|
+
if (!id.startsWith(prefix))
|
|
206
|
+
continue;
|
|
207
|
+
const n = Number(id.slice(prefix.length));
|
|
208
|
+
if (Number.isInteger(n) && n > max)
|
|
209
|
+
max = n;
|
|
210
|
+
}
|
|
211
|
+
return max + 1;
|
|
212
|
+
}
|
|
117
213
|
export function protectedClonableChildIds(dag, predecessorId, envelope) {
|
|
118
214
|
const ids = new Set();
|
|
119
215
|
for (const childId of dag.childrenOf[predecessorId] ?? []) {
|
|
@@ -126,15 +222,35 @@ export function protectedClonableChildIds(dag, predecessorId, envelope) {
|
|
|
126
222
|
}
|
|
127
223
|
return ids;
|
|
128
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Downstream closure for a retry, resolved through clone-instance ids and
|
|
227
|
+
* static multi-parent edges. Catalog-level children (e.g. a join stage) are
|
|
228
|
+
* registered in `childrenOf` under the *definition* id, not per clone
|
|
229
|
+
* instance, so a plain childrenOf walk starting above a fanout dead-ends at
|
|
230
|
+
* the instance ids it discovers transitively. Resolving definition-id
|
|
231
|
+
* children and `predecessorEdges` join successors at every step means
|
|
232
|
+
* retrying an ancestor or a single clone instance still reaches the join
|
|
233
|
+
* and everything after it, without pulling in sibling terminals.
|
|
234
|
+
*/
|
|
129
235
|
export function cloneRetryDownstream(dag, stageIds) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
236
|
+
const snapshot = asDagSnapshot(dag);
|
|
237
|
+
const downstream = new Set();
|
|
238
|
+
const seeds = new Set(stageIds);
|
|
239
|
+
const queue = [...stageIds];
|
|
240
|
+
while (queue.length > 0) {
|
|
241
|
+
const id = queue.shift();
|
|
242
|
+
const defId = definitionIdForInstance(snapshot, id);
|
|
243
|
+
const children = new Set([
|
|
244
|
+
...(dag.childrenOf[id] ?? []),
|
|
245
|
+
...(defId !== id ? dag.childrenOf[defId] ?? [] : []),
|
|
246
|
+
...joinSuccessorIds(dag, id),
|
|
247
|
+
...(defId !== id ? joinSuccessorIds(dag, defId) : []),
|
|
248
|
+
]);
|
|
249
|
+
for (const child of children) {
|
|
250
|
+
if (downstream.has(child) || seeds.has(child))
|
|
251
|
+
continue;
|
|
134
252
|
downstream.add(child);
|
|
135
|
-
|
|
136
|
-
downstream.add(desc);
|
|
137
|
-
}
|
|
253
|
+
queue.push(child);
|
|
138
254
|
}
|
|
139
255
|
}
|
|
140
256
|
return downstream;
|
|
@@ -160,30 +276,53 @@ export function cloneFailFastSkipIds(dag, stageId, completedEnvelopes) {
|
|
|
160
276
|
ids.push(...joinAndDownstreamIds(dag, stageId));
|
|
161
277
|
return ids;
|
|
162
278
|
}
|
|
163
|
-
export function cloneScheduleAllowsRun(dag, stageId, states, completedEnvelopes) {
|
|
279
|
+
export function cloneScheduleAllowsRun(dag, stageId, states, completedEnvelopes, options) {
|
|
164
280
|
const node = dag.nodes.find((n) => n.id === stageId);
|
|
165
281
|
if (!node)
|
|
166
282
|
return false;
|
|
167
283
|
if (definitionInstances(dag, stageId).some((id) => id !== stageId)) {
|
|
168
284
|
return false;
|
|
169
285
|
}
|
|
170
|
-
|
|
286
|
+
const edges = predecessorEdges(node);
|
|
287
|
+
if (edges.length === 0) {
|
|
171
288
|
return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
|
|
172
289
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
290
|
+
if (edges.length >= 2) {
|
|
291
|
+
for (const edge of edges) {
|
|
292
|
+
if (!edgeInstancesReady(dag, edge, states))
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
178
295
|
}
|
|
179
|
-
else
|
|
180
|
-
|
|
296
|
+
else {
|
|
297
|
+
const parentNeedId = typeof node.needs === "string" && node.needs ? node.needs : edges[0].id;
|
|
298
|
+
const allInstances = definitionInstances(dag, parentNeedId);
|
|
299
|
+
const cohort = options?.activeCohortForNeeds?.(parentNeedId) ??
|
|
300
|
+
activeCohortFromCloneIds(options?.activeCloneIdsForNeeds?.(parentNeedId));
|
|
301
|
+
if (cohort.kind === "awaiting_mint") {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
const instances = filterJoinInputs(allInstances, cohort);
|
|
305
|
+
if (instances.length <= 1) {
|
|
306
|
+
const parentId = instances[0] ?? parentNeedId;
|
|
307
|
+
if (states.get(parentId) !== "succeeded")
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
else if (cohort.kind === "active") {
|
|
311
|
+
if (!instances.every((id) => states.get(id) === "succeeded")) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (!instances.every((id) => states.get(id) === "succeeded" || states.get(id) === "skipped")) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
181
318
|
}
|
|
182
319
|
return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
|
|
183
320
|
}
|
|
184
|
-
export function applyCloneForksToSchedule(dag, predecessorId, envelope, states) {
|
|
321
|
+
export function applyCloneForksToSchedule(dag, predecessorId, envelope, states, options) {
|
|
185
322
|
let current = asDagSnapshot(dag);
|
|
186
323
|
const skippedIds = [];
|
|
324
|
+
const mintedBySuccessor = new Map();
|
|
325
|
+
const forceFresh = options?.forceFreshCloneIds === true;
|
|
187
326
|
const skipPending = (stageId) => {
|
|
188
327
|
if (states.get(stageId) === "pending") {
|
|
189
328
|
states.set(stageId, "skipped");
|
|
@@ -193,15 +332,55 @@ export function applyCloneForksToSchedule(dag, predecessorId, envelope, states)
|
|
|
193
332
|
for (const item of envelope.clone_forks ?? []) {
|
|
194
333
|
if (item.action === "skip") {
|
|
195
334
|
skipPending(item.successor_id);
|
|
196
|
-
|
|
197
|
-
skipPending(desc);
|
|
198
|
-
}
|
|
335
|
+
skipRejectedNeedDependents(current, item.successor_id, states, "skipped", skipPending);
|
|
199
336
|
continue;
|
|
200
337
|
}
|
|
201
338
|
if (item.action !== "fanout")
|
|
202
339
|
continue;
|
|
203
|
-
const existing = definitionInstances(current, item.successor_id);
|
|
204
|
-
if (
|
|
340
|
+
const existing = definitionInstances(current, item.successor_id).filter((id) => id !== item.successor_id);
|
|
341
|
+
if (forceFresh) {
|
|
342
|
+
const { snapshot, instanceIds } = appendCloneInstances(current, {
|
|
343
|
+
catalogId: item.successor_id,
|
|
344
|
+
predecessorId,
|
|
345
|
+
count: item.clones.length,
|
|
346
|
+
startAt: nextFreeCloneSuffix(current, item.successor_id),
|
|
347
|
+
});
|
|
348
|
+
current = snapshot;
|
|
349
|
+
states.delete(item.successor_id);
|
|
350
|
+
for (const id of instanceIds) {
|
|
351
|
+
states.set(id, "pending");
|
|
352
|
+
}
|
|
353
|
+
mintedBySuccessor.set(item.successor_id, instanceIds);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (existing.length > 0) {
|
|
357
|
+
// Reactivating instances a retry cascade reset to `pending` (see
|
|
358
|
+
// cloneFanoutConflict), which only allows this fanout through when
|
|
359
|
+
// *every* existing instance is pending. Anything short of that —
|
|
360
|
+
// a normal resume re-apply of an already-processed fanout, or only
|
|
361
|
+
// one sibling out of a cohort being retried directly — must stay a
|
|
362
|
+
// no-op here: growing/shrinking the cohort in those cases would mint
|
|
363
|
+
// or skip instances no retry actually asked for.
|
|
364
|
+
const allPending = existing.every((id) => states.get(id) === "pending");
|
|
365
|
+
if (!allPending)
|
|
366
|
+
continue;
|
|
367
|
+
const desired = item.clones.length;
|
|
368
|
+
for (const id of existing.slice(desired)) {
|
|
369
|
+
skipPending(id);
|
|
370
|
+
skipRejectedNeedDependents(current, id, states, "skipped", skipPending);
|
|
371
|
+
}
|
|
372
|
+
if (desired > existing.length) {
|
|
373
|
+
const { snapshot, instanceIds } = appendCloneInstances(current, {
|
|
374
|
+
catalogId: item.successor_id,
|
|
375
|
+
predecessorId,
|
|
376
|
+
count: desired - existing.length,
|
|
377
|
+
startAt: nextFreeCloneSuffix(current, item.successor_id),
|
|
378
|
+
});
|
|
379
|
+
current = snapshot;
|
|
380
|
+
for (const id of instanceIds) {
|
|
381
|
+
states.set(id, "pending");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
205
384
|
continue;
|
|
206
385
|
}
|
|
207
386
|
const { snapshot, instanceIds } = appendCloneInstances(current, {
|
|
@@ -214,8 +393,13 @@ export function applyCloneForksToSchedule(dag, predecessorId, envelope, states)
|
|
|
214
393
|
for (const id of instanceIds) {
|
|
215
394
|
states.set(id, "pending");
|
|
216
395
|
}
|
|
396
|
+
mintedBySuccessor.set(item.successor_id, instanceIds);
|
|
217
397
|
}
|
|
218
|
-
return {
|
|
398
|
+
return {
|
|
399
|
+
dag: current,
|
|
400
|
+
skippedIds,
|
|
401
|
+
...(mintedBySuccessor.size > 0 ? { mintedBySuccessor } : {}),
|
|
402
|
+
};
|
|
219
403
|
}
|
|
220
404
|
export function applyCloneForksFromEnvelopes(dag, states, completedEnvelopes) {
|
|
221
405
|
let current = asDagSnapshot(dag);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { CompletionCheck, CompletionContract } from "../types/completion.js";
|
|
2
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
|
+
import type { StageGateKind } from "../types/stage.js";
|
|
4
|
+
/**
|
|
5
|
+
* The result of executing a pipeline-owned command. Command execution is a
|
|
6
|
+
* runtime capability so callers can supply a sandboxed implementation.
|
|
7
|
+
*/
|
|
8
|
+
export type CommandExecution = {
|
|
9
|
+
exit_code: number | null;
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
timed_out: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
};
|
|
15
|
+
export type CommandExecutionInput = {
|
|
16
|
+
command: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
timeout_ms?: number;
|
|
19
|
+
};
|
|
20
|
+
export type CommandExecutor = {
|
|
21
|
+
run(input: CommandExecutionInput): Promise<CommandExecution>;
|
|
22
|
+
};
|
|
23
|
+
/** A completed operator interaction, normalized outside the agent adapter. */
|
|
24
|
+
export type GateDecision = {
|
|
25
|
+
kind: StageGateKind;
|
|
26
|
+
status: "accepted" | "rejected" | "answered";
|
|
27
|
+
prompt_id?: string;
|
|
28
|
+
};
|
|
29
|
+
export type GateDecisionProvider = {
|
|
30
|
+
listDecisions(kind: StageGateKind): Promise<readonly GateDecision[]>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A VCS-neutral checkout baseline. `fingerprint` is owned by the provider:
|
|
34
|
+
* for example, a blob hash, an mtime/content hash pair, or a Git index value.
|
|
35
|
+
*/
|
|
36
|
+
export type CheckoutSnapshot = {
|
|
37
|
+
entries: readonly {
|
|
38
|
+
path: string;
|
|
39
|
+
fingerprint: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
export type CheckoutChange = {
|
|
43
|
+
path: string;
|
|
44
|
+
status: "added" | "modified" | "deleted" | "renamed" | "untracked";
|
|
45
|
+
};
|
|
46
|
+
export type CheckoutCapability = {
|
|
47
|
+
capture(): Promise<CheckoutSnapshot>;
|
|
48
|
+
changesSince(before: CheckoutSnapshot): Promise<readonly CheckoutChange[]>;
|
|
49
|
+
};
|
|
50
|
+
/** The agent's explicit acknowledgement of every item in one checklist. */
|
|
51
|
+
export type ChecklistAttestation = {
|
|
52
|
+
check_id: string;
|
|
53
|
+
items: readonly string[];
|
|
54
|
+
};
|
|
55
|
+
export type CompletionCheckRunnerInput = {
|
|
56
|
+
contract: CompletionContract;
|
|
57
|
+
envelope: StageEnvelope;
|
|
58
|
+
/** The stage's declared payload_schema, when it has one. */
|
|
59
|
+
payloadSchema?: unknown;
|
|
60
|
+
/** Absolute attempt artifact directory. */
|
|
61
|
+
artifactsDir: string;
|
|
62
|
+
/** Base directory used for a command check without its own cwd. */
|
|
63
|
+
commandWorkingDirectory?: string;
|
|
64
|
+
commandExecutor?: CommandExecutor;
|
|
65
|
+
gates?: GateDecisionProvider;
|
|
66
|
+
/** Captured before the agent attempt began. */
|
|
67
|
+
checkoutBefore?: CheckoutSnapshot;
|
|
68
|
+
checkout?: CheckoutCapability;
|
|
69
|
+
/** Why the runtime could not establish the required pre-stage baseline. */
|
|
70
|
+
checkoutError?: string;
|
|
71
|
+
checklistAttestations?: readonly ChecklistAttestation[];
|
|
72
|
+
onCheckStart?: (check: CompletionCheck) => Promise<void> | void;
|
|
73
|
+
onCheckComplete?: (result: CompletionCheckResult) => Promise<void> | void;
|
|
74
|
+
};
|
|
75
|
+
export type CommandCheckEvidence = {
|
|
76
|
+
kind: "command";
|
|
77
|
+
command: string;
|
|
78
|
+
cwd?: string;
|
|
79
|
+
exit_code: number | null;
|
|
80
|
+
stdout: string;
|
|
81
|
+
stderr: string;
|
|
82
|
+
timed_out: boolean;
|
|
83
|
+
error?: string;
|
|
84
|
+
};
|
|
85
|
+
export type ArtifactCheckEvidence = {
|
|
86
|
+
kind: "artifact";
|
|
87
|
+
path: string;
|
|
88
|
+
exists: boolean;
|
|
89
|
+
file_type: "file" | "directory" | "symlink" | "other" | "missing";
|
|
90
|
+
size_bytes?: number;
|
|
91
|
+
sha256?: string;
|
|
92
|
+
};
|
|
93
|
+
export type PayloadSchemaCheckEvidence = {
|
|
94
|
+
kind: "payload_schema";
|
|
95
|
+
schema_declared: boolean;
|
|
96
|
+
payload_present: boolean;
|
|
97
|
+
validation_error?: string;
|
|
98
|
+
};
|
|
99
|
+
export type GateCheckEvidence = {
|
|
100
|
+
kind: "gate";
|
|
101
|
+
gate_kind: StageGateKind;
|
|
102
|
+
decisions: GateDecision[];
|
|
103
|
+
};
|
|
104
|
+
export type CheckoutChangesCheckEvidence = {
|
|
105
|
+
kind: "checkout_changes";
|
|
106
|
+
changes: CheckoutChange[];
|
|
107
|
+
claimed_paths?: string[];
|
|
108
|
+
missing_claimed_paths?: string[];
|
|
109
|
+
unclaimed_changed_paths?: string[];
|
|
110
|
+
};
|
|
111
|
+
export type ChecklistCheckEvidence = {
|
|
112
|
+
kind: "checklist";
|
|
113
|
+
expected_items: string[];
|
|
114
|
+
attested_items: string[];
|
|
115
|
+
missing_items: string[];
|
|
116
|
+
unexpected_items: string[];
|
|
117
|
+
};
|
|
118
|
+
export type CompletionCheckEvidence = CommandCheckEvidence | ArtifactCheckEvidence | PayloadSchemaCheckEvidence | GateCheckEvidence | CheckoutChangesCheckEvidence | ChecklistCheckEvidence;
|
|
119
|
+
export type CompletionCheckResult = {
|
|
120
|
+
check_id: string;
|
|
121
|
+
check_type: CompletionCheck["type"];
|
|
122
|
+
outcome: "passed" | "failed" | "error";
|
|
123
|
+
message?: string;
|
|
124
|
+
evidence: CompletionCheckEvidence;
|
|
125
|
+
};
|
|
126
|
+
export type CompletionVerificationResult = {
|
|
127
|
+
outcome: "passed" | "failed";
|
|
128
|
+
checks: CompletionCheckResult[];
|
|
129
|
+
failed_check_ids: string[];
|
|
130
|
+
};
|
|
131
|
+
/** Default Node implementation; callers may substitute a sandboxed executor. */
|
|
132
|
+
export declare const nodeCommandExecutor: CommandExecutor;
|
|
133
|
+
/**
|
|
134
|
+
* Run every completion check in declaration order. A failed check never throws
|
|
135
|
+
* or prevents later checks from producing their own repair evidence.
|
|
136
|
+
*/
|
|
137
|
+
export declare function runCompletionContract(input: CompletionCheckRunnerInput): Promise<CompletionVerificationResult>;
|