@chllming/wave-orchestration 0.7.0 → 0.7.2
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/CHANGELOG.md +40 -0
- package/README.md +9 -8
- package/docs/guides/planner.md +19 -0
- package/docs/guides/terminal-surfaces.md +12 -0
- package/docs/plans/component-cutover-matrix.json +50 -3
- package/docs/plans/current-state.md +1 -1
- package/docs/plans/end-state-architecture.md +927 -0
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +26 -0
- package/docs/plans/wave-orchestrator.md +4 -7
- package/docs/plans/waves/wave-1.md +376 -0
- package/docs/plans/waves/wave-2.md +292 -0
- package/docs/plans/waves/wave-3.md +342 -0
- package/docs/plans/waves/wave-4.md +391 -0
- package/docs/plans/waves/wave-5.md +382 -0
- package/docs/plans/waves/wave-6.md +321 -0
- package/docs/reference/cli-reference.md +547 -0
- package/docs/reference/coordination-and-closure.md +1 -1
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/runtime-config/README.md +2 -2
- package/docs/reference/runtime-config/codex.md +2 -1
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +43 -2
- package/scripts/wave-orchestrator/agent-state.mjs +458 -35
- package/scripts/wave-orchestrator/artifact-schemas.mjs +81 -0
- package/scripts/wave-orchestrator/control-cli.mjs +119 -20
- package/scripts/wave-orchestrator/coordination.mjs +11 -10
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +82 -2
- package/scripts/wave-orchestrator/human-input-workflow.mjs +289 -0
- package/scripts/wave-orchestrator/install.mjs +120 -3
- package/scripts/wave-orchestrator/launcher-derived-state.mjs +915 -0
- package/scripts/wave-orchestrator/launcher-gates.mjs +1061 -0
- package/scripts/wave-orchestrator/launcher-retry.mjs +873 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +9 -9
- package/scripts/wave-orchestrator/launcher-supervisor.mjs +704 -0
- package/scripts/wave-orchestrator/launcher.mjs +317 -2999
- package/scripts/wave-orchestrator/task-entity.mjs +557 -0
- package/scripts/wave-orchestrator/terminals.mjs +1 -1
- package/scripts/wave-orchestrator/wave-files.mjs +138 -20
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +566 -0
- package/wave.config.json +1 -1
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { materializeControlPlaneState } from "./control-plane.mjs";
|
|
2
|
+
import {
|
|
3
|
+
buildCoordinationResponseMetrics,
|
|
4
|
+
isOpenCoordinationStatus,
|
|
5
|
+
materializeCoordinationState,
|
|
6
|
+
openClarificationLinkedRequests,
|
|
7
|
+
} from "./coordination-store.mjs";
|
|
8
|
+
import {
|
|
9
|
+
buildTasksFromWaveDefinition,
|
|
10
|
+
buildTasksFromCoordinationState,
|
|
11
|
+
mergeTaskSets,
|
|
12
|
+
evaluateOwnedSliceProven,
|
|
13
|
+
} from "./task-entity.mjs";
|
|
14
|
+
import {
|
|
15
|
+
buildGateSnapshotPure,
|
|
16
|
+
readWaveImplementationGatePure,
|
|
17
|
+
readWaveContQaGatePure,
|
|
18
|
+
readWaveContEvalGatePure,
|
|
19
|
+
readWaveComponentGatePure,
|
|
20
|
+
readWaveComponentMatrixGatePure,
|
|
21
|
+
readWaveDocumentationGatePure,
|
|
22
|
+
readWaveSecurityGatePure,
|
|
23
|
+
readWaveIntegrationGatePure,
|
|
24
|
+
readWaveInfraGatePure,
|
|
25
|
+
} from "./launcher-gates.mjs";
|
|
26
|
+
import {
|
|
27
|
+
validateImplementationSummary,
|
|
28
|
+
validateContQaSummary,
|
|
29
|
+
validateContEvalSummary,
|
|
30
|
+
validateDocumentationClosureSummary,
|
|
31
|
+
validateSecuritySummary,
|
|
32
|
+
validateIntegrationSummary,
|
|
33
|
+
} from "./agent-state.mjs";
|
|
34
|
+
import {
|
|
35
|
+
isSecurityReviewAgent,
|
|
36
|
+
isContEvalImplementationOwningAgent,
|
|
37
|
+
} from "./role-helpers.mjs";
|
|
38
|
+
|
|
39
|
+
const REDUCER_VERSION = 1;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Derive the wave phase from the current state.
|
|
43
|
+
*
|
|
44
|
+
* This reuses the same logic as ledger.mjs derivePhase, adapted for
|
|
45
|
+
* the reducer's data structures.
|
|
46
|
+
*/
|
|
47
|
+
function derivePhase({
|
|
48
|
+
tasks,
|
|
49
|
+
gateSnapshot,
|
|
50
|
+
coordinationState,
|
|
51
|
+
dependencySnapshot,
|
|
52
|
+
}) {
|
|
53
|
+
const blockers = (coordinationState?.blockers || []).filter(
|
|
54
|
+
(record) =>
|
|
55
|
+
isOpenCoordinationStatus(record.status) &&
|
|
56
|
+
["high", "urgent"].includes(record.priority),
|
|
57
|
+
);
|
|
58
|
+
if (blockers.length > 0) {
|
|
59
|
+
return "blocked";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const openClarifications = (coordinationState?.clarifications || []).filter(
|
|
63
|
+
(record) => isOpenCoordinationStatus(record.status),
|
|
64
|
+
);
|
|
65
|
+
const openClarificationRequests = openClarificationLinkedRequests(coordinationState);
|
|
66
|
+
if (openClarifications.length > 0 || openClarificationRequests.length > 0) {
|
|
67
|
+
return "clarifying";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const dependencyBlockers =
|
|
71
|
+
(dependencySnapshot?.requiredInbound || []).length +
|
|
72
|
+
(dependencySnapshot?.requiredOutbound || []).length;
|
|
73
|
+
if (dependencyBlockers > 0) {
|
|
74
|
+
return "blocked";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const blockingHelperTasks = (tasks || []).filter(
|
|
78
|
+
(task) =>
|
|
79
|
+
["helper", "dependency"].includes(task.taskType) &&
|
|
80
|
+
task.closureState === "open",
|
|
81
|
+
);
|
|
82
|
+
if (blockingHelperTasks.length > 0) {
|
|
83
|
+
return "blocked";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ((tasks || []).length === 0) {
|
|
87
|
+
return "running";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const implementationTasks = (tasks || []).filter(
|
|
91
|
+
(task) => task.taskType === "implementation",
|
|
92
|
+
);
|
|
93
|
+
const allImplementationProven = implementationTasks.every(
|
|
94
|
+
(task) =>
|
|
95
|
+
task.closureState === "owned_slice_proven" ||
|
|
96
|
+
task.closureState === "wave_closure_ready" ||
|
|
97
|
+
task.closureState === "closed",
|
|
98
|
+
);
|
|
99
|
+
if (!allImplementationProven && implementationTasks.length > 0) {
|
|
100
|
+
return "running";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (gateSnapshot?.contEvalGate && !gateSnapshot.contEvalGate.ok) {
|
|
104
|
+
return "cont-eval";
|
|
105
|
+
}
|
|
106
|
+
if (gateSnapshot?.securityGate && !gateSnapshot.securityGate.ok) {
|
|
107
|
+
return "security-review";
|
|
108
|
+
}
|
|
109
|
+
if (gateSnapshot?.integrationBarrier && !gateSnapshot.integrationBarrier.ok) {
|
|
110
|
+
return "integrating";
|
|
111
|
+
}
|
|
112
|
+
if (gateSnapshot?.documentationGate && !gateSnapshot.documentationGate.ok) {
|
|
113
|
+
return "docs-closure";
|
|
114
|
+
}
|
|
115
|
+
if (gateSnapshot?.contQaGate && !gateSnapshot.contQaGate.ok) {
|
|
116
|
+
return "cont-qa-closure";
|
|
117
|
+
}
|
|
118
|
+
if (gateSnapshot?.overall?.ok) {
|
|
119
|
+
return "completed";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return "running";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build proof availability per agent from agent results and tasks.
|
|
127
|
+
*/
|
|
128
|
+
function buildProofAvailability(tasks, agentResults, controlPlaneState) {
|
|
129
|
+
const byAgentId = {};
|
|
130
|
+
const agentTasks = new Map();
|
|
131
|
+
|
|
132
|
+
for (const task of tasks || []) {
|
|
133
|
+
const agentId = task.assigneeAgentId || task.ownerAgentId;
|
|
134
|
+
if (!agentId) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!agentTasks.has(agentId)) {
|
|
138
|
+
agentTasks.set(agentId, []);
|
|
139
|
+
}
|
|
140
|
+
agentTasks.get(agentId).push(task);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const activeProofBundles = controlPlaneState?.activeProofBundles || [];
|
|
144
|
+
const proofBundlesByAgentId = new Map();
|
|
145
|
+
for (const bundle of activeProofBundles) {
|
|
146
|
+
const agentId = bundle.agentId || bundle.data?.agentId;
|
|
147
|
+
if (!agentId) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!proofBundlesByAgentId.has(agentId)) {
|
|
151
|
+
proofBundlesByAgentId.set(agentId, []);
|
|
152
|
+
}
|
|
153
|
+
proofBundlesByAgentId.get(agentId).push(bundle);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const [agentId, agentTaskList] of agentTasks) {
|
|
157
|
+
const result = agentResults?.[agentId] || null;
|
|
158
|
+
const proofBundleIds = (proofBundlesByAgentId.get(agentId) || []).map(
|
|
159
|
+
(bundle) => bundle.id,
|
|
160
|
+
);
|
|
161
|
+
let ownedSliceProven = true;
|
|
162
|
+
let exitContractMet = true;
|
|
163
|
+
let deliverablesMet = true;
|
|
164
|
+
let componentsMet = true;
|
|
165
|
+
let proofArtifactsMet = true;
|
|
166
|
+
|
|
167
|
+
for (const task of agentTaskList) {
|
|
168
|
+
const evaluation = evaluateOwnedSliceProven(task, result);
|
|
169
|
+
if (!evaluation.proven) {
|
|
170
|
+
ownedSliceProven = false;
|
|
171
|
+
}
|
|
172
|
+
if (
|
|
173
|
+
task.proofRequirements?.includes("implementation-exit-met") &&
|
|
174
|
+
!evaluation.proven
|
|
175
|
+
) {
|
|
176
|
+
exitContractMet = false;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
task.proofRequirements?.includes("component-level-met") &&
|
|
180
|
+
!evaluation.proven
|
|
181
|
+
) {
|
|
182
|
+
componentsMet = false;
|
|
183
|
+
}
|
|
184
|
+
if (
|
|
185
|
+
task.proofRequirements?.includes("proof-artifacts-present") &&
|
|
186
|
+
!evaluation.proven
|
|
187
|
+
) {
|
|
188
|
+
proofArtifactsMet = false;
|
|
189
|
+
deliverablesMet = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
byAgentId[agentId] = {
|
|
194
|
+
ownedSliceProven,
|
|
195
|
+
exitContractMet,
|
|
196
|
+
deliverablesMet,
|
|
197
|
+
componentsMet,
|
|
198
|
+
proofArtifactsMet,
|
|
199
|
+
proofBundleIds,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const allOwnedSlicesProven = Object.values(byAgentId).every(
|
|
204
|
+
(entry) => entry.ownedSliceProven,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
byAgentId,
|
|
209
|
+
allOwnedSlicesProven,
|
|
210
|
+
activeProofBundles,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Derive open blockers from coordination state and gate snapshot.
|
|
216
|
+
*/
|
|
217
|
+
function deriveOpenBlockers(coordinationState, gateSnapshot) {
|
|
218
|
+
const blockers = [];
|
|
219
|
+
|
|
220
|
+
for (const record of coordinationState?.blockers || []) {
|
|
221
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
blockers.push({
|
|
225
|
+
kind: "coordination-blocker",
|
|
226
|
+
id: record.id,
|
|
227
|
+
detail: record.summary || record.detail || "",
|
|
228
|
+
blockedAgentIds: record.targets || [],
|
|
229
|
+
resolutionHint: record.resolutionHint || null,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const record of coordinationState?.clarifications || []) {
|
|
234
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
blockers.push({
|
|
238
|
+
kind: "clarification",
|
|
239
|
+
id: record.id,
|
|
240
|
+
detail: record.summary || record.detail || "",
|
|
241
|
+
blockedAgentIds: record.targets || [],
|
|
242
|
+
resolutionHint: "Resolve clarification before proceeding.",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const record of coordinationState?.humanEscalations || []) {
|
|
247
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
blockers.push({
|
|
251
|
+
kind: "human-escalation",
|
|
252
|
+
id: record.id,
|
|
253
|
+
detail: record.summary || record.detail || "",
|
|
254
|
+
blockedAgentIds: record.targets || [],
|
|
255
|
+
resolutionHint: "Human intervention required.",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const record of coordinationState?.humanFeedback || []) {
|
|
260
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
blockers.push({
|
|
264
|
+
kind: "human-feedback",
|
|
265
|
+
id: record.id,
|
|
266
|
+
detail: record.summary || record.detail || "",
|
|
267
|
+
blockedAgentIds: record.targets || [],
|
|
268
|
+
resolutionHint: "Awaiting human feedback.",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (gateSnapshot) {
|
|
273
|
+
for (const [gateName, gate] of Object.entries(gateSnapshot)) {
|
|
274
|
+
if (gateName === "overall" || !gate || gate.ok !== false) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
blockers.push({
|
|
278
|
+
kind: "gate-failure",
|
|
279
|
+
id: gateName,
|
|
280
|
+
detail: gate.detail || gate.statusCode || "",
|
|
281
|
+
blockedAgentIds: gate.agentId ? [gate.agentId] : [],
|
|
282
|
+
resolutionHint: `Gate ${gateName} must pass before wave closure.`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return blockers;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Derive retry target set from gate snapshot and proof availability.
|
|
292
|
+
*/
|
|
293
|
+
function deriveRetryTargetSet(gateSnapshot, proofAvailability) {
|
|
294
|
+
const failedAgentIds = [];
|
|
295
|
+
let reason = "";
|
|
296
|
+
|
|
297
|
+
for (const [agentId, entry] of Object.entries(proofAvailability.byAgentId || {})) {
|
|
298
|
+
if (!entry.ownedSliceProven) {
|
|
299
|
+
failedAgentIds.push(agentId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (failedAgentIds.length > 0) {
|
|
304
|
+
reason = `Agent(s) ${failedAgentIds.join(", ")} did not prove their owned slices.`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
agentIds: failedAgentIds,
|
|
309
|
+
reason,
|
|
310
|
+
retryOverride: null,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Derive closure eligibility from gate snapshot and tasks.
|
|
316
|
+
*/
|
|
317
|
+
function deriveClosureEligibility(gateSnapshot, tasks, proofAvailability) {
|
|
318
|
+
const allGatesPass = gateSnapshot?.overall?.ok === true;
|
|
319
|
+
const allTasksClosed = (tasks || []).every(
|
|
320
|
+
(task) =>
|
|
321
|
+
task.closureState === "closed" ||
|
|
322
|
+
task.closureState === "cancelled" ||
|
|
323
|
+
task.closureState === "superseded",
|
|
324
|
+
);
|
|
325
|
+
const allTasksClosureReady = (tasks || []).every(
|
|
326
|
+
(task) =>
|
|
327
|
+
task.closureState === "wave_closure_ready" ||
|
|
328
|
+
task.closureState === "closed" ||
|
|
329
|
+
task.closureState === "cancelled" ||
|
|
330
|
+
task.closureState === "superseded",
|
|
331
|
+
);
|
|
332
|
+
const waveMayClose = allGatesPass && (allTasksClosed || allTasksClosureReady);
|
|
333
|
+
|
|
334
|
+
const ownedSliceProvenAgentIds = [];
|
|
335
|
+
const pendingAgentIds = [];
|
|
336
|
+
for (const [agentId, entry] of Object.entries(proofAvailability.byAgentId || {})) {
|
|
337
|
+
if (entry.ownedSliceProven) {
|
|
338
|
+
ownedSliceProvenAgentIds.push(agentId);
|
|
339
|
+
} else {
|
|
340
|
+
pendingAgentIds.push(agentId);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
allGatesPass,
|
|
346
|
+
allTasksClosed,
|
|
347
|
+
waveMayClose,
|
|
348
|
+
ownedSliceProvenAgentIds,
|
|
349
|
+
pendingAgentIds,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Mark tasks with updated closure states based on proof availability.
|
|
355
|
+
*/
|
|
356
|
+
function applyProofAvailabilityToTasks(tasks, proofAvailability) {
|
|
357
|
+
return (tasks || []).map((task) => {
|
|
358
|
+
const agentId = task.assigneeAgentId || task.ownerAgentId;
|
|
359
|
+
if (!agentId) {
|
|
360
|
+
return task;
|
|
361
|
+
}
|
|
362
|
+
const entry = proofAvailability.byAgentId?.[agentId];
|
|
363
|
+
if (!entry) {
|
|
364
|
+
return task;
|
|
365
|
+
}
|
|
366
|
+
if (task.closureState === "open" && entry.ownedSliceProven) {
|
|
367
|
+
return { ...task, closureState: "owned_slice_proven" };
|
|
368
|
+
}
|
|
369
|
+
return task;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* reduceWaveState - Pure reducer function.
|
|
375
|
+
*
|
|
376
|
+
* Takes pre-read inputs and produces a complete WaveState snapshot.
|
|
377
|
+
* No file I/O.
|
|
378
|
+
*/
|
|
379
|
+
export function reduceWaveState({
|
|
380
|
+
controlPlaneEvents = [],
|
|
381
|
+
coordinationRecords = [],
|
|
382
|
+
agentResults = {},
|
|
383
|
+
waveDefinition = null,
|
|
384
|
+
dependencyTickets = null,
|
|
385
|
+
feedbackRequests = [],
|
|
386
|
+
laneConfig = {},
|
|
387
|
+
}) {
|
|
388
|
+
// Step 1: Materialize control-plane state
|
|
389
|
+
const controlPlaneState = materializeControlPlaneState(controlPlaneEvents);
|
|
390
|
+
|
|
391
|
+
// Step 2: Materialize coordination state
|
|
392
|
+
const coordinationState = materializeCoordinationState(coordinationRecords);
|
|
393
|
+
|
|
394
|
+
// Step 3: Build tasks
|
|
395
|
+
const seedTasks = buildTasksFromWaveDefinition(waveDefinition, laneConfig);
|
|
396
|
+
const coordinationTasks = buildTasksFromCoordinationState(
|
|
397
|
+
coordinationState,
|
|
398
|
+
feedbackRequests,
|
|
399
|
+
);
|
|
400
|
+
let tasks = mergeTaskSets(seedTasks, coordinationTasks);
|
|
401
|
+
|
|
402
|
+
// Step 4: Evaluate proof availability per agent
|
|
403
|
+
const proofAvailability = buildProofAvailability(
|
|
404
|
+
tasks,
|
|
405
|
+
agentResults,
|
|
406
|
+
controlPlaneState,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Apply proof state to tasks (auto-transition from open -> owned_slice_proven)
|
|
410
|
+
tasks = applyProofAvailabilityToTasks(tasks, proofAvailability);
|
|
411
|
+
|
|
412
|
+
// Step 5: Build derived state for barriers
|
|
413
|
+
const clarificationBarrier = (() => {
|
|
414
|
+
const openClarifications = (coordinationState?.clarifications || []).filter(
|
|
415
|
+
(record) => isOpenCoordinationStatus(record.status),
|
|
416
|
+
);
|
|
417
|
+
if (openClarifications.length > 0) {
|
|
418
|
+
return {
|
|
419
|
+
ok: false,
|
|
420
|
+
statusCode: "clarification-open",
|
|
421
|
+
detail: `Open clarifications remain (${openClarifications.map((record) => record.id).join(", ")}).`,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const openClarificationReqs = openClarificationLinkedRequests(coordinationState);
|
|
425
|
+
if (openClarificationReqs.length > 0) {
|
|
426
|
+
return {
|
|
427
|
+
ok: false,
|
|
428
|
+
statusCode: "clarification-follow-up-open",
|
|
429
|
+
detail: `Clarification follow-up requests remain open (${openClarificationReqs.map((record) => record.id).join(", ")}).`,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const pendingHuman = [
|
|
433
|
+
...(coordinationState?.humanEscalations || []).filter((record) =>
|
|
434
|
+
isOpenCoordinationStatus(record.status),
|
|
435
|
+
),
|
|
436
|
+
...(coordinationState?.humanFeedback || []).filter((record) =>
|
|
437
|
+
isOpenCoordinationStatus(record.status),
|
|
438
|
+
),
|
|
439
|
+
];
|
|
440
|
+
if (pendingHuman.length > 0) {
|
|
441
|
+
return {
|
|
442
|
+
ok: false,
|
|
443
|
+
statusCode: "human-feedback-open",
|
|
444
|
+
detail: `Pending human input remains (${pendingHuman.map((record) => record.id).join(", ")}).`,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return { ok: true, statusCode: "pass", detail: "" };
|
|
448
|
+
})();
|
|
449
|
+
|
|
450
|
+
const helperAssignmentBarrier = { ok: true, statusCode: "pass", detail: "" };
|
|
451
|
+
const dependencyBarrier = (() => {
|
|
452
|
+
if (!dependencyTickets) {
|
|
453
|
+
return { ok: true, statusCode: "pass", detail: "" };
|
|
454
|
+
}
|
|
455
|
+
const requiredInbound = dependencyTickets.requiredInbound || [];
|
|
456
|
+
const requiredOutbound = dependencyTickets.requiredOutbound || [];
|
|
457
|
+
const unresolvedInboundAssignments =
|
|
458
|
+
dependencyTickets.unresolvedInboundAssignments || [];
|
|
459
|
+
if (unresolvedInboundAssignments.length > 0) {
|
|
460
|
+
return {
|
|
461
|
+
ok: false,
|
|
462
|
+
statusCode: "dependency-assignment-unresolved",
|
|
463
|
+
detail: `Required inbound dependencies are unassigned (${unresolvedInboundAssignments.map((record) => record.id).join(", ")}).`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (requiredInbound.length > 0 || requiredOutbound.length > 0) {
|
|
467
|
+
return {
|
|
468
|
+
ok: false,
|
|
469
|
+
statusCode: "dependency-open",
|
|
470
|
+
detail: `Open required dependencies remain (${[...requiredInbound, ...requiredOutbound].map((record) => record.id).join(", ")}).`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return { ok: true, statusCode: "pass", detail: "" };
|
|
474
|
+
})();
|
|
475
|
+
|
|
476
|
+
const derivedState = {
|
|
477
|
+
clarificationBarrier,
|
|
478
|
+
helperAssignmentBarrier,
|
|
479
|
+
dependencyBarrier,
|
|
480
|
+
integrationSummary: null,
|
|
481
|
+
coordinationState,
|
|
482
|
+
dependencySnapshot: dependencyTickets,
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// Try to derive integration summary from agent results
|
|
486
|
+
const integrationAgentId = laneConfig.integrationAgentId || "A8";
|
|
487
|
+
const integrationSummary = agentResults?.[integrationAgentId]?.integration || null;
|
|
488
|
+
if (integrationSummary) {
|
|
489
|
+
derivedState.integrationSummary = {
|
|
490
|
+
recommendation: integrationSummary.state === "ready-for-doc-closure"
|
|
491
|
+
? "ready-for-doc-closure"
|
|
492
|
+
: integrationSummary.state || "needs-more-work",
|
|
493
|
+
detail: integrationSummary.detail || null,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Step 5: Evaluate gates using pure variants
|
|
498
|
+
const gateSnapshot = buildGateSnapshotPure({
|
|
499
|
+
wave: waveDefinition || { wave: 0, agents: [] },
|
|
500
|
+
agentResults,
|
|
501
|
+
derivedState,
|
|
502
|
+
validationMode: laneConfig.validationMode || "live",
|
|
503
|
+
laneConfig,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Step 6: Derive open blockers
|
|
507
|
+
const openBlockers = deriveOpenBlockers(coordinationState, gateSnapshot);
|
|
508
|
+
|
|
509
|
+
// Step 7: Derive retry target set
|
|
510
|
+
const retryTargetSet = deriveRetryTargetSet(gateSnapshot, proofAvailability);
|
|
511
|
+
|
|
512
|
+
// Step 8: Derive closure eligibility
|
|
513
|
+
const closureEligibility = deriveClosureEligibility(
|
|
514
|
+
gateSnapshot,
|
|
515
|
+
tasks,
|
|
516
|
+
proofAvailability,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// Step 9: Derive phase
|
|
520
|
+
const phase = derivePhase({
|
|
521
|
+
tasks,
|
|
522
|
+
gateSnapshot,
|
|
523
|
+
coordinationState,
|
|
524
|
+
dependencySnapshot: dependencyTickets,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Build coordination metrics
|
|
528
|
+
const coordinationMetrics = buildCoordinationResponseMetrics(coordinationState);
|
|
529
|
+
|
|
530
|
+
// Build tasksByAgentId
|
|
531
|
+
const tasksByAgentId = {};
|
|
532
|
+
for (const task of tasks) {
|
|
533
|
+
const agentId = task.assigneeAgentId || task.ownerAgentId;
|
|
534
|
+
if (!agentId) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (!tasksByAgentId[agentId]) {
|
|
538
|
+
tasksByAgentId[agentId] = [];
|
|
539
|
+
}
|
|
540
|
+
tasksByAgentId[agentId].push(task);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
reducerVersion: REDUCER_VERSION,
|
|
545
|
+
wave: waveDefinition?.wave ?? 0,
|
|
546
|
+
lane: laneConfig.lane || "main",
|
|
547
|
+
attempt: controlPlaneState?.activeAttempt?.attempt ?? 0,
|
|
548
|
+
phase,
|
|
549
|
+
|
|
550
|
+
tasks,
|
|
551
|
+
tasksByAgentId,
|
|
552
|
+
|
|
553
|
+
proofAvailability,
|
|
554
|
+
|
|
555
|
+
openBlockers,
|
|
556
|
+
|
|
557
|
+
gateSnapshot,
|
|
558
|
+
|
|
559
|
+
retryTargetSet,
|
|
560
|
+
|
|
561
|
+
closureEligibility,
|
|
562
|
+
|
|
563
|
+
coordinationMetrics,
|
|
564
|
+
controlPlaneState,
|
|
565
|
+
};
|
|
566
|
+
}
|