@chllming/wave-orchestration 0.8.3 → 0.8.4
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 +19 -0
- package/README.md +47 -11
- package/docs/README.md +6 -2
- package/docs/concepts/what-is-a-wave.md +1 -1
- package/docs/plans/architecture-hardening-migration.md +8 -1
- package/docs/plans/current-state.md +15 -7
- package/docs/plans/end-state-architecture.md +82 -69
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +235 -62
- package/docs/plans/wave-orchestrator.md +37 -11
- package/docs/reference/cli-reference.md +34 -14
- package/docs/reference/coordination-and-closure.md +19 -6
- package/docs/reference/npmjs-trusted-publishing.md +5 -4
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +20 -0
- package/scripts/wave-orchestrator/agent-state.mjs +0 -491
- package/scripts/wave-orchestrator/autonomous.mjs +10 -6
- package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
- package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
- package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +395 -139
- package/scripts/wave-orchestrator/human-input-resolution.mjs +14 -10
- package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
- package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +5 -6
- package/scripts/wave-orchestrator/launcher.mjs +271 -724
- package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
- package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
- package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
- package/scripts/wave-orchestrator/replay.mjs +3 -1
- package/scripts/wave-orchestrator/result-envelope.mjs +589 -0
- package/scripts/wave-orchestrator/retry-control.mjs +5 -0
- package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +267 -18
- package/scripts/wave-orchestrator/role-helpers.mjs +51 -0
- package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/traces.mjs +10 -1
- package/scripts/wave-orchestrator/wave-files.mjs +11 -9
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +52 -5
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
readStatusCodeIfPresent,
|
|
16
16
|
} from "./dashboard-state.mjs";
|
|
17
|
+
import { appendWaveControlEvent } from "./control-plane.mjs";
|
|
17
18
|
import {
|
|
18
19
|
REPO_ROOT,
|
|
19
20
|
readJsonOrNull,
|
|
21
|
+
readStatusRecordIfPresent,
|
|
20
22
|
ensureDirectory,
|
|
21
23
|
shellQuote,
|
|
22
24
|
PACKAGE_ROOT,
|
|
@@ -31,8 +33,8 @@ import {
|
|
|
31
33
|
} from "./terminals.mjs";
|
|
32
34
|
import {
|
|
33
35
|
recordGlobalDashboardEvent,
|
|
34
|
-
writeGlobalDashboard,
|
|
35
36
|
} from "./dashboard-state.mjs";
|
|
37
|
+
import { buildHumanFeedbackWorkflowUpdate } from "./human-input-workflow.mjs";
|
|
36
38
|
import {
|
|
37
39
|
collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
|
|
38
40
|
launchAgentSession as launchAgentSessionImpl,
|
|
@@ -51,6 +53,108 @@ function isProcessAlive(pid) {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
function relativeArtifactPath(filePath) {
|
|
57
|
+
return filePath ? path.relative(REPO_ROOT, filePath) : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function recordWaveRunState(lanePaths, waveNumber, state, data = {}) {
|
|
61
|
+
return appendWaveControlEvent(lanePaths, waveNumber, {
|
|
62
|
+
entityType: "wave_run",
|
|
63
|
+
entityId: `wave-${waveNumber}`,
|
|
64
|
+
action: state,
|
|
65
|
+
source: "session-supervisor",
|
|
66
|
+
actor: "session-supervisor",
|
|
67
|
+
data: {
|
|
68
|
+
waveId: `wave-${waveNumber}`,
|
|
69
|
+
waveNumber,
|
|
70
|
+
updatedAt: toIsoTimestamp(),
|
|
71
|
+
...data,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function recordAttemptState(lanePaths, waveNumber, attemptNumber, state, data = {}) {
|
|
77
|
+
return appendWaveControlEvent(lanePaths, waveNumber, {
|
|
78
|
+
entityType: "attempt",
|
|
79
|
+
entityId: `wave-${waveNumber}-attempt-${attemptNumber}`,
|
|
80
|
+
action: state,
|
|
81
|
+
source: "session-supervisor",
|
|
82
|
+
actor: "session-supervisor",
|
|
83
|
+
attempt: attemptNumber,
|
|
84
|
+
data: {
|
|
85
|
+
attemptId: `wave-${waveNumber}-attempt-${attemptNumber}`,
|
|
86
|
+
attemptNumber,
|
|
87
|
+
state,
|
|
88
|
+
selectedAgentIds: data.selectedAgentIds || [],
|
|
89
|
+
detail: data.detail || null,
|
|
90
|
+
updatedAt: toIsoTimestamp(),
|
|
91
|
+
...(data.createdAt ? { createdAt: data.createdAt } : {}),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function recordAgentRunStarted(lanePaths, { waveNumber, attempt, runInfo }) {
|
|
97
|
+
if (!runInfo?.agent?.agentId || !Number.isFinite(Number(waveNumber))) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return appendWaveControlEvent(lanePaths, waveNumber, {
|
|
101
|
+
entityType: "agent_run",
|
|
102
|
+
entityId: `wave-${waveNumber}-attempt-${attempt}-agent-${runInfo.agent.agentId}`,
|
|
103
|
+
action: "started",
|
|
104
|
+
source: "session-supervisor",
|
|
105
|
+
actor: "session-supervisor",
|
|
106
|
+
attempt,
|
|
107
|
+
data: {
|
|
108
|
+
agentId: runInfo.agent.agentId,
|
|
109
|
+
attemptNumber: attempt,
|
|
110
|
+
sessionName: runInfo.sessionName || null,
|
|
111
|
+
executorId: runInfo.lastExecutorId || runInfo.agent.executorResolved?.id || null,
|
|
112
|
+
promptPath: relativeArtifactPath(runInfo.promptPath),
|
|
113
|
+
statusPath: relativeArtifactPath(runInfo.statusPath),
|
|
114
|
+
logPath: relativeArtifactPath(runInfo.logPath),
|
|
115
|
+
startedAt: toIsoTimestamp(),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function recordAgentRunFinished(
|
|
121
|
+
lanePaths,
|
|
122
|
+
{ waveNumber, attempt, runInfo, failure = null, statusRecord = null },
|
|
123
|
+
) {
|
|
124
|
+
if (!runInfo?.agent?.agentId || !Number.isFinite(Number(waveNumber))) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const effectiveStatusRecord = statusRecord || readStatusRecordIfPresent(runInfo.statusPath);
|
|
128
|
+
const timedOut =
|
|
129
|
+
failure?.statusCode === "timeout-no-status" || failure?.statusCode === "timed_out";
|
|
130
|
+
const action =
|
|
131
|
+
timedOut
|
|
132
|
+
? "timed_out"
|
|
133
|
+
: Number(effectiveStatusRecord?.code) === 0
|
|
134
|
+
? "completed"
|
|
135
|
+
: "failed";
|
|
136
|
+
return appendWaveControlEvent(lanePaths, waveNumber, {
|
|
137
|
+
entityType: "agent_run",
|
|
138
|
+
entityId: `wave-${waveNumber}-attempt-${attempt}-agent-${runInfo.agent.agentId}`,
|
|
139
|
+
action,
|
|
140
|
+
source: "session-supervisor",
|
|
141
|
+
actor: "session-supervisor",
|
|
142
|
+
attempt,
|
|
143
|
+
data: {
|
|
144
|
+
agentId: runInfo.agent.agentId,
|
|
145
|
+
attemptNumber: attempt,
|
|
146
|
+
exitCode: effectiveStatusRecord?.code ?? null,
|
|
147
|
+
completedAt: effectiveStatusRecord?.completedAt || toIsoTimestamp(),
|
|
148
|
+
promptHash: effectiveStatusRecord?.promptHash || runInfo.lastPromptHash || null,
|
|
149
|
+
executorId: runInfo.lastExecutorId || runInfo.agent.executorResolved?.id || null,
|
|
150
|
+
statusCode: failure?.statusCode || null,
|
|
151
|
+
detail: failure?.detail || null,
|
|
152
|
+
logPath: relativeArtifactPath(runInfo.logPath),
|
|
153
|
+
statusPath: relativeArtifactPath(runInfo.statusPath),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
54
158
|
export function markLauncherFailed(
|
|
55
159
|
globalDashboard,
|
|
56
160
|
lanePaths,
|
|
@@ -64,7 +168,6 @@ export function markLauncherFailed(
|
|
|
64
168
|
level: "error",
|
|
65
169
|
message: error instanceof Error ? error.message : String(error),
|
|
66
170
|
});
|
|
67
|
-
writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
|
|
68
171
|
}
|
|
69
172
|
appendCoordination({
|
|
70
173
|
event: "launcher_finish",
|
|
@@ -553,13 +656,46 @@ export function launchWaveDashboardSession(lanePaths, { sessionName, dashboardPa
|
|
|
553
656
|
}
|
|
554
657
|
|
|
555
658
|
export async function launchAgentSession(lanePaths, params) {
|
|
556
|
-
|
|
659
|
+
const result = await launchAgentSessionImpl(lanePaths, params, { runTmuxFn: runTmux });
|
|
660
|
+
const controlPlane = params?.controlPlane || null;
|
|
661
|
+
if (!params?.dryRun && controlPlane?.waveNumber !== undefined && controlPlane?.attempt) {
|
|
662
|
+
recordAgentRunStarted(lanePaths, {
|
|
663
|
+
waveNumber: controlPlane.waveNumber,
|
|
664
|
+
attempt: controlPlane.attempt,
|
|
665
|
+
runInfo: {
|
|
666
|
+
...params,
|
|
667
|
+
lastExecutorId: result?.executorId || params?.agent?.executorResolved?.id || null,
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
557
672
|
}
|
|
558
673
|
|
|
559
|
-
export async function waitForWaveCompletion(
|
|
560
|
-
|
|
674
|
+
export async function waitForWaveCompletion(
|
|
675
|
+
lanePaths,
|
|
676
|
+
agentRuns,
|
|
677
|
+
timeoutMinutes,
|
|
678
|
+
onProgress = null,
|
|
679
|
+
options = {},
|
|
680
|
+
) {
|
|
681
|
+
const result = await waitForWaveCompletionImpl(lanePaths, agentRuns, timeoutMinutes, onProgress, {
|
|
561
682
|
collectUnexpectedSessionFailuresFn: collectUnexpectedSessionFailures,
|
|
562
683
|
});
|
|
684
|
+
const controlPlane = options?.controlPlane || null;
|
|
685
|
+
if (controlPlane?.waveNumber !== undefined && controlPlane?.attempt) {
|
|
686
|
+
const failuresByAgentId = new Map(
|
|
687
|
+
(result?.failures || []).map((failure) => [failure.agentId, failure]),
|
|
688
|
+
);
|
|
689
|
+
for (const runInfo of agentRuns || []) {
|
|
690
|
+
recordAgentRunFinished(lanePaths, {
|
|
691
|
+
waveNumber: controlPlane.waveNumber,
|
|
692
|
+
attempt: controlPlane.attempt,
|
|
693
|
+
runInfo,
|
|
694
|
+
failure: failuresByAgentId.get(runInfo.agent.agentId) || null,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return result;
|
|
563
699
|
}
|
|
564
700
|
|
|
565
701
|
export function monitorWaveHumanFeedback({
|
|
@@ -592,111 +728,50 @@ export function monitorWaveHumanFeedback({
|
|
|
592
728
|
const context = request.context ? `; context=${request.context}` : "";
|
|
593
729
|
const responseOperator = request.responseOperator || "human-operator";
|
|
594
730
|
const responseText = request.responseText || "(empty response)";
|
|
731
|
+
const escalationId = `escalation-${request.id}`;
|
|
732
|
+
const existingEscalation =
|
|
733
|
+
(fs.existsSync(triageLogPath)
|
|
734
|
+
? readMaterializedCoordinationState(triageLogPath).byId.get(escalationId)
|
|
735
|
+
: null) ||
|
|
736
|
+
readMaterializedCoordinationState(coordinationLogPath).byId.get(escalationId) ||
|
|
737
|
+
null;
|
|
738
|
+
const workflowUpdate = buildHumanFeedbackWorkflowUpdate({
|
|
739
|
+
request,
|
|
740
|
+
lane: lanePaths.lane,
|
|
741
|
+
waveNumber,
|
|
742
|
+
existingEscalation,
|
|
743
|
+
});
|
|
595
744
|
if (request.status === "pending") {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
`[human-feedback] respond with: pnpm exec wave control task act answer --lane ${lanePaths.lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
|
|
606
|
-
);
|
|
607
|
-
appendCoordination({
|
|
608
|
-
event: "human_feedback_requested",
|
|
609
|
-
waves: [waveNumber],
|
|
610
|
-
status: "waiting-human",
|
|
611
|
-
details: `request_id=${request.id}; agent=${request.agentId}; question=${question}${context}`,
|
|
612
|
-
actionRequested: `Launcher operator should ask or answer in the parent session, then run: pnpm exec wave control task act answer --lane ${lanePaths.lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
|
|
613
|
-
});
|
|
745
|
+
if (workflowUpdate?.combinedEvent) {
|
|
746
|
+
recordCombinedEvent(workflowUpdate.combinedEvent);
|
|
747
|
+
}
|
|
748
|
+
for (const line of workflowUpdate?.consoleLines || []) {
|
|
749
|
+
console.warn(line);
|
|
750
|
+
}
|
|
751
|
+
if (workflowUpdate?.coordinationNotice) {
|
|
752
|
+
appendCoordination(workflowUpdate.coordinationNotice);
|
|
753
|
+
}
|
|
614
754
|
if (coordinationLogPath) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
wave: waveNumber,
|
|
619
|
-
agentId: request.agentId || "human",
|
|
620
|
-
kind: "human-feedback",
|
|
621
|
-
targets: request.agentId ? [`agent:${request.agentId}`] : [],
|
|
622
|
-
priority: "high",
|
|
623
|
-
summary: question,
|
|
624
|
-
detail: request.context || "",
|
|
625
|
-
status: "open",
|
|
626
|
-
source: "feedback",
|
|
627
|
-
});
|
|
755
|
+
for (const update of workflowUpdate?.coordinationUpdates || []) {
|
|
756
|
+
appendCoordinationRecord(coordinationLogPath, update);
|
|
757
|
+
}
|
|
628
758
|
}
|
|
629
759
|
} else if (request.status === "answered") {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
event: "human_feedback_answered",
|
|
637
|
-
waves: [waveNumber],
|
|
638
|
-
status: "resolved",
|
|
639
|
-
details: `request_id=${request.id}; agent=${request.agentId}; operator=${responseOperator}; response=${responseText}`,
|
|
640
|
-
});
|
|
760
|
+
if (workflowUpdate?.combinedEvent) {
|
|
761
|
+
recordCombinedEvent(workflowUpdate.combinedEvent);
|
|
762
|
+
}
|
|
763
|
+
if (workflowUpdate?.coordinationNotice) {
|
|
764
|
+
appendCoordination(workflowUpdate.coordinationNotice);
|
|
765
|
+
}
|
|
641
766
|
if (coordinationLogPath) {
|
|
642
|
-
const escalationId = `escalation-${request.id}`;
|
|
643
|
-
const existingEscalation =
|
|
644
|
-
(fs.existsSync(triageLogPath)
|
|
645
|
-
? readMaterializedCoordinationState(triageLogPath).byId.get(escalationId)
|
|
646
|
-
: null) ||
|
|
647
|
-
readMaterializedCoordinationState(coordinationLogPath).byId.get(escalationId) ||
|
|
648
|
-
null;
|
|
649
767
|
if (fs.existsSync(triageLogPath)) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
targets:
|
|
657
|
-
existingEscalation?.targets ||
|
|
658
|
-
(request.agentId ? [`agent:${request.agentId}`] : []),
|
|
659
|
-
dependsOn: existingEscalation?.dependsOn || [],
|
|
660
|
-
closureCondition: existingEscalation?.closureCondition || "",
|
|
661
|
-
priority: "high",
|
|
662
|
-
summary: question,
|
|
663
|
-
detail: responseText,
|
|
664
|
-
artifactRefs: [request.id],
|
|
665
|
-
status: "resolved",
|
|
666
|
-
source: "feedback",
|
|
667
|
-
});
|
|
768
|
+
for (const update of workflowUpdate?.triageUpdates || []) {
|
|
769
|
+
appendCoordinationRecord(triageLogPath, update);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const update of workflowUpdate?.coordinationUpdates || []) {
|
|
773
|
+
appendCoordinationRecord(coordinationLogPath, update);
|
|
668
774
|
}
|
|
669
|
-
appendCoordinationRecord(coordinationLogPath, {
|
|
670
|
-
id: escalationId,
|
|
671
|
-
lane: lanePaths.lane,
|
|
672
|
-
wave: waveNumber,
|
|
673
|
-
agentId: request.agentId || "human",
|
|
674
|
-
kind: "human-escalation",
|
|
675
|
-
targets:
|
|
676
|
-
existingEscalation?.targets ||
|
|
677
|
-
(request.agentId ? [`agent:${request.agentId}`] : []),
|
|
678
|
-
dependsOn: existingEscalation?.dependsOn || [],
|
|
679
|
-
closureCondition: existingEscalation?.closureCondition || "",
|
|
680
|
-
priority: "high",
|
|
681
|
-
summary: question,
|
|
682
|
-
detail: responseText,
|
|
683
|
-
artifactRefs: [request.id],
|
|
684
|
-
status: "resolved",
|
|
685
|
-
source: "feedback",
|
|
686
|
-
});
|
|
687
|
-
appendCoordinationRecord(coordinationLogPath, {
|
|
688
|
-
id: request.id,
|
|
689
|
-
lane: lanePaths.lane,
|
|
690
|
-
wave: waveNumber,
|
|
691
|
-
agentId: request.agentId || "human",
|
|
692
|
-
kind: "human-feedback",
|
|
693
|
-
targets: request.agentId ? [`agent:${request.agentId}`] : [],
|
|
694
|
-
priority: "high",
|
|
695
|
-
summary: question,
|
|
696
|
-
detail: responseText,
|
|
697
|
-
status: "resolved",
|
|
698
|
-
source: "feedback",
|
|
699
|
-
});
|
|
700
775
|
}
|
|
701
776
|
}
|
|
702
777
|
}
|
|
@@ -201,6 +201,7 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
|
|
|
201
201
|
inboxesDir: path.join(stateDir, "inboxes"),
|
|
202
202
|
ledgerDir: path.join(stateDir, "ledger"),
|
|
203
203
|
integrationDir: path.join(stateDir, "integration"),
|
|
204
|
+
resultsDir: path.join(stateDir, "results"),
|
|
204
205
|
proofDir: path.join(stateDir, "proof"),
|
|
205
206
|
securityDir: path.join(stateDir, "security"),
|
|
206
207
|
dependencySnapshotsDir: path.join(stateDir, "dependencies"),
|
|
@@ -55,6 +55,9 @@ function fileHashOrNull(filePath) {
|
|
|
55
55
|
if (!filePath || !fs.existsSync(filePath)) {
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
+
if (!fs.statSync(filePath).isFile()) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
58
61
|
return hashText(fs.readFileSync(filePath, "utf8"));
|
|
59
62
|
}
|
|
60
63
|
|
|
@@ -62,6 +65,9 @@ function copyFileIfExists(sourcePath, destPath) {
|
|
|
62
65
|
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
63
66
|
return false;
|
|
64
67
|
}
|
|
68
|
+
if (!fs.statSync(sourcePath).isFile()) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
65
71
|
ensureDirectory(path.dirname(destPath));
|
|
66
72
|
fs.copyFileSync(sourcePath, destPath);
|
|
67
73
|
return true;
|
|
@@ -603,7 +609,10 @@ function normalizeGateLogPath(gate, agentArtifacts) {
|
|
|
603
609
|
}
|
|
604
610
|
return {
|
|
605
611
|
...gate,
|
|
606
|
-
|
|
612
|
+
// Log artifacts are already described in the bundle manifest. Nulling the
|
|
613
|
+
// inline path keeps replay parity focused on gate semantics instead of the
|
|
614
|
+
// copied artifact layout.
|
|
615
|
+
logPath: null,
|
|
607
616
|
};
|
|
608
617
|
}
|
|
609
618
|
|
|
@@ -1253,7 +1253,11 @@ export function loadComponentCutoverMatrix(options = {}) {
|
|
|
1253
1253
|
const matrixJsonPath =
|
|
1254
1254
|
options.componentMatrixJsonPath ||
|
|
1255
1255
|
(laneProfile
|
|
1256
|
-
? path.resolve(
|
|
1256
|
+
? path.resolve(
|
|
1257
|
+
REPO_ROOT,
|
|
1258
|
+
laneProfile?.paths?.componentCutoverMatrixJsonPath ||
|
|
1259
|
+
"trace-bundle/component-cutover-matrix.json",
|
|
1260
|
+
)
|
|
1257
1261
|
: "trace-bundle/component-cutover-matrix.json");
|
|
1258
1262
|
const payload =
|
|
1259
1263
|
options.componentMatrixPayload !== undefined
|
|
@@ -1366,8 +1370,8 @@ export function requiredDocumentationStewardPathsForWave(waveNumber, options = {
|
|
|
1366
1370
|
const componentThreshold = laneProfile.validation.requireComponentPromotionsFromWave;
|
|
1367
1371
|
if (componentThreshold !== null && waveNumber >= componentThreshold) {
|
|
1368
1372
|
out.push(
|
|
1369
|
-
laneProfile
|
|
1370
|
-
laneProfile
|
|
1373
|
+
laneProfile?.paths?.componentCutoverMatrixDocPath,
|
|
1374
|
+
laneProfile?.paths?.componentCutoverMatrixJsonPath,
|
|
1371
1375
|
);
|
|
1372
1376
|
}
|
|
1373
1377
|
return Array.from(new Set(out));
|
|
@@ -2353,18 +2357,16 @@ export function validateWaveComponentMatrixCurrentLevels(wave, options = {}) {
|
|
|
2353
2357
|
securityRolePromptPath,
|
|
2354
2358
|
}),
|
|
2355
2359
|
);
|
|
2356
|
-
if (
|
|
2357
|
-
promotions.length === 0 &&
|
|
2358
|
-
((componentThreshold === null || wave.wave < componentThreshold) ||
|
|
2359
|
-
implementationOwningAgents.length === 0)
|
|
2360
|
-
) {
|
|
2360
|
+
if (promotions.length === 0) {
|
|
2361
2361
|
return {
|
|
2362
2362
|
ok: true,
|
|
2363
2363
|
statusCode: "pass",
|
|
2364
2364
|
detail:
|
|
2365
2365
|
implementationOwningAgents.length === 0
|
|
2366
2366
|
? `Wave ${wave.wave} has no implementation-owned component promotions to reconcile.`
|
|
2367
|
-
:
|
|
2367
|
+
: componentThreshold === null || wave.wave < componentThreshold
|
|
2368
|
+
? "Component current-level gate is not active for this wave."
|
|
2369
|
+
: `Wave ${wave.wave} declares no promoted components to reconcile against the component matrix.`,
|
|
2368
2370
|
componentId: null,
|
|
2369
2371
|
};
|
|
2370
2372
|
}
|
|
@@ -16,12 +16,27 @@ import {
|
|
|
16
16
|
buildGateSnapshotPure,
|
|
17
17
|
readClarificationBarrier,
|
|
18
18
|
readWaveAssignmentBarrier,
|
|
19
|
-
} from "./
|
|
19
|
+
} from "./gate-engine.mjs";
|
|
20
20
|
import { buildHumanInputRequests } from "./human-input-workflow.mjs";
|
|
21
|
+
import { projectLegacySummaryFromEnvelope } from "./result-envelope.mjs";
|
|
21
22
|
import { buildRequestAssignments } from "./routing-state.mjs";
|
|
23
|
+
import { resolveSecurityReviewReportPath } from "./role-helpers.mjs";
|
|
22
24
|
|
|
23
25
|
const REDUCER_VERSION = 2;
|
|
24
26
|
|
|
27
|
+
function resolveReducerReportPath(waveDefinition, agent) {
|
|
28
|
+
if (!waveDefinition || !agent) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (agent.agentId === (waveDefinition.contQaAgentId || "A0")) {
|
|
32
|
+
return waveDefinition.contQaReportPath || null;
|
|
33
|
+
}
|
|
34
|
+
if (agent.agentId === (waveDefinition.contEvalAgentId || "E0")) {
|
|
35
|
+
return waveDefinition.contEvalReportPath || null;
|
|
36
|
+
}
|
|
37
|
+
return resolveSecurityReviewReportPath(agent);
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
/**
|
|
26
41
|
* Detect contradictions from control-plane events.
|
|
27
42
|
* Returns a Map<contradictionId, contradiction>.
|
|
@@ -117,6 +132,7 @@ function derivePhase({
|
|
|
117
132
|
gateSnapshot,
|
|
118
133
|
coordinationState,
|
|
119
134
|
dependencySnapshot,
|
|
135
|
+
clarificationBarrier,
|
|
120
136
|
}) {
|
|
121
137
|
const blockers = (coordinationState?.blockers || []).filter(
|
|
122
138
|
(record) =>
|
|
@@ -134,6 +150,9 @@ function derivePhase({
|
|
|
134
150
|
if (openClarifications.length > 0 || openClarificationRequests.length > 0) {
|
|
135
151
|
return "clarifying";
|
|
136
152
|
}
|
|
153
|
+
if (clarificationBarrier?.statusCode === "human-feedback-open") {
|
|
154
|
+
return "clarifying";
|
|
155
|
+
}
|
|
137
156
|
|
|
138
157
|
const dependencyBlockers =
|
|
139
158
|
(dependencySnapshot?.requiredInbound || []).length +
|
|
@@ -528,12 +547,34 @@ function applyProofAvailabilityToTasks(tasks, proofAvailability) {
|
|
|
528
547
|
export function reduceWaveState({
|
|
529
548
|
controlPlaneEvents = [],
|
|
530
549
|
coordinationRecords = [],
|
|
531
|
-
|
|
550
|
+
agentEnvelopes = null,
|
|
551
|
+
agentResults = null,
|
|
532
552
|
waveDefinition = null,
|
|
533
553
|
dependencyTickets = null,
|
|
534
554
|
feedbackRequests = [],
|
|
535
555
|
laneConfig = {},
|
|
536
556
|
}) {
|
|
557
|
+
const resolvedAgentResults =
|
|
558
|
+
agentResults && typeof agentResults === "object" && !Array.isArray(agentResults)
|
|
559
|
+
? agentResults
|
|
560
|
+
: Object.fromEntries(
|
|
561
|
+
Object.entries(agentEnvelopes || {})
|
|
562
|
+
.map(([agentId, envelope]) => {
|
|
563
|
+
const agent =
|
|
564
|
+
(Array.isArray(waveDefinition?.agents) ? waveDefinition.agents : []).find(
|
|
565
|
+
(candidate) => candidate?.agentId === agentId,
|
|
566
|
+
) || { agentId };
|
|
567
|
+
return [
|
|
568
|
+
agentId,
|
|
569
|
+
projectLegacySummaryFromEnvelope(envelope, {
|
|
570
|
+
agent,
|
|
571
|
+
waveNumber: waveDefinition?.wave ?? null,
|
|
572
|
+
reportPath: resolveReducerReportPath(waveDefinition, agent),
|
|
573
|
+
}),
|
|
574
|
+
];
|
|
575
|
+
})
|
|
576
|
+
.filter(([, summary]) => Boolean(summary)),
|
|
577
|
+
);
|
|
537
578
|
// Step 1: Materialize control-plane state
|
|
538
579
|
const controlPlaneState = materializeControlPlaneState(controlPlaneEvents);
|
|
539
580
|
|
|
@@ -555,7 +596,7 @@ export function reduceWaveState({
|
|
|
555
596
|
// Step 5: Evaluate proof availability per agent
|
|
556
597
|
const proofAvailability = buildProofAvailability(
|
|
557
598
|
tasks,
|
|
558
|
-
|
|
599
|
+
resolvedAgentResults,
|
|
559
600
|
controlPlaneState,
|
|
560
601
|
);
|
|
561
602
|
|
|
@@ -564,7 +605,7 @@ export function reduceWaveState({
|
|
|
564
605
|
|
|
565
606
|
// Step 6: Build integration summary BEFORE creating derivedState for gates
|
|
566
607
|
const integrationAgentId = laneConfig.integrationAgentId || "A8";
|
|
567
|
-
const integrationResult =
|
|
608
|
+
const integrationResult = resolvedAgentResults?.[integrationAgentId]?.integration || null;
|
|
568
609
|
const integrationSummary = integrationResult
|
|
569
610
|
? {
|
|
570
611
|
recommendation: integrationResult.state === "ready-for-doc-closure"
|
|
@@ -622,7 +663,7 @@ export function reduceWaveState({
|
|
|
622
663
|
// Step 8: Evaluate gates using pure variants (integrationSummary already in derivedState)
|
|
623
664
|
const gateSnapshot = buildGateSnapshotPure({
|
|
624
665
|
wave: waveDefinition || { wave: 0, agents: [] },
|
|
625
|
-
agentResults,
|
|
666
|
+
agentResults: resolvedAgentResults,
|
|
626
667
|
derivedState,
|
|
627
668
|
validationMode: laneConfig.validationMode || "live",
|
|
628
669
|
laneConfig,
|
|
@@ -647,6 +688,7 @@ export function reduceWaveState({
|
|
|
647
688
|
gateSnapshot,
|
|
648
689
|
coordinationState,
|
|
649
690
|
dependencySnapshot: dependencyTickets,
|
|
691
|
+
clarificationBarrier,
|
|
650
692
|
});
|
|
651
693
|
|
|
652
694
|
// Step 13: Derive waveState from phase
|
|
@@ -708,6 +750,11 @@ export function reduceWaveState({
|
|
|
708
750
|
facts,
|
|
709
751
|
humanInputs,
|
|
710
752
|
assignments,
|
|
753
|
+
agentEnvelopes: agentEnvelopes || null,
|
|
754
|
+
capabilityAssignments,
|
|
755
|
+
coordinationState,
|
|
756
|
+
dependencySnapshot: dependencyTickets,
|
|
757
|
+
integrationSummary,
|
|
711
758
|
|
|
712
759
|
coordinationMetrics,
|
|
713
760
|
controlPlaneState,
|