@chllming/wave-orchestration 0.9.1 → 0.9.3
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 +52 -1
- package/LICENSE.md +21 -0
- package/README.md +20 -9
- package/docs/README.md +8 -4
- package/docs/agents/wave-security-role.md +1 -0
- package/docs/architecture/README.md +1 -1
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/guides/author-and-run-waves.md +1 -1
- package/docs/guides/planner.md +2 -2
- package/docs/guides/{recommendations-0.9.1.md → recommendations-0.9.2.md} +7 -7
- package/docs/guides/recommendations-0.9.3.md +137 -0
- package/docs/guides/sandboxed-environments.md +2 -2
- package/docs/plans/current-state.md +8 -2
- package/docs/plans/end-state-architecture.md +1 -1
- package/docs/plans/examples/wave-example-design-handoff.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +65 -67
- package/docs/reference/cli-reference.md +1 -1
- package/docs/reference/coordination-and-closure.md +20 -3
- package/docs/reference/corridor.md +225 -0
- package/docs/reference/npmjs-token-publishing.md +2 -2
- package/docs/reference/package-publishing-flow.md +11 -11
- package/docs/reference/runtime-config/README.md +61 -3
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +1 -1
- package/docs/reference/wave-control.md +358 -27
- package/docs/roadmap.md +12 -19
- package/package.json +1 -1
- package/releases/manifest.json +44 -3
- package/scripts/wave-cli-bootstrap.mjs +52 -1
- package/scripts/wave-orchestrator/agent-state.mjs +26 -9
- package/scripts/wave-orchestrator/config.mjs +199 -3
- package/scripts/wave-orchestrator/context7.mjs +231 -29
- package/scripts/wave-orchestrator/coordination.mjs +15 -1
- package/scripts/wave-orchestrator/corridor.mjs +363 -0
- package/scripts/wave-orchestrator/derived-state-engine.mjs +38 -1
- package/scripts/wave-orchestrator/gate-engine.mjs +20 -0
- package/scripts/wave-orchestrator/install.mjs +34 -1
- package/scripts/wave-orchestrator/launcher-runtime.mjs +111 -7
- package/scripts/wave-orchestrator/launcher.mjs +21 -3
- package/scripts/wave-orchestrator/planner.mjs +30 -0
- package/scripts/wave-orchestrator/projection-writer.mjs +23 -0
- package/scripts/wave-orchestrator/provider-runtime.mjs +104 -0
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/traces.mjs +25 -0
- package/scripts/wave-orchestrator/wave-control-client.mjs +14 -1
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_CORRIDOR_BASE_URL,
|
|
5
|
+
DEFAULT_CORRIDOR_SEVERITY_THRESHOLD,
|
|
6
|
+
DEFAULT_WAVE_CONTROL_ENDPOINT,
|
|
7
|
+
} from "./config.mjs";
|
|
8
|
+
import { writeJsonAtomic, readJsonOrNull } from "./shared.mjs";
|
|
9
|
+
import {
|
|
10
|
+
isDefaultWaveControlEndpoint,
|
|
11
|
+
readJsonResponse,
|
|
12
|
+
resolveWaveControlAuthToken,
|
|
13
|
+
} from "./provider-runtime.mjs";
|
|
14
|
+
import {
|
|
15
|
+
isContEvalImplementationOwningAgent,
|
|
16
|
+
isDesignAgent,
|
|
17
|
+
isSecurityReviewAgent,
|
|
18
|
+
} from "./role-helpers.mjs";
|
|
19
|
+
|
|
20
|
+
const SEVERITY_RANK = {
|
|
21
|
+
low: 1,
|
|
22
|
+
medium: 2,
|
|
23
|
+
high: 3,
|
|
24
|
+
critical: 4,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function normalizeOwnedPath(value) {
|
|
28
|
+
return String(value || "").trim().replaceAll("\\", "/").replace(/\/+$/, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isRelevantOwnedPath(value) {
|
|
32
|
+
const normalized = normalizeOwnedPath(value);
|
|
33
|
+
if (!normalized || normalized.startsWith(".tmp/")) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (normalized.startsWith("docs/")) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return !/\.(?:md|txt)$/i.test(normalized);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function matchesOwnedPath(findingPath, ownedPath) {
|
|
43
|
+
const normalizedFinding = normalizeOwnedPath(findingPath);
|
|
44
|
+
const normalizedOwned = normalizeOwnedPath(ownedPath);
|
|
45
|
+
if (!normalizedFinding || !normalizedOwned) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return normalizedFinding === normalizedOwned || normalizedFinding.startsWith(`${normalizedOwned}/`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shouldIncludeImplementationOwnedPaths(agent, lanePaths = {}) {
|
|
52
|
+
if (!agent || isSecurityReviewAgent(agent) || isDesignAgent(agent)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (agent.agentId === lanePaths.contQaAgentId || agent.agentId === lanePaths.documentationAgentId) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (agent.agentId === lanePaths.integrationAgentId) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (agent.agentId === lanePaths.contEvalAgentId) {
|
|
62
|
+
return isContEvalImplementationOwningAgent(agent, {
|
|
63
|
+
contEvalAgentId: lanePaths.contEvalAgentId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function waveCorridorContextPath(lanePaths, waveNumber) {
|
|
70
|
+
return path.join(lanePaths.securityDir, `wave-${waveNumber}-corridor.json`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function readWaveCorridorContext(lanePaths, waveNumber) {
|
|
74
|
+
return readJsonOrNull(waveCorridorContextPath(lanePaths, waveNumber));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function corridorArtifactBase({ lanePaths, wave, ownedPaths, providerMode, source }) {
|
|
78
|
+
return {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
wave,
|
|
81
|
+
lane: lanePaths.lane,
|
|
82
|
+
projectId: lanePaths.project,
|
|
83
|
+
providerMode,
|
|
84
|
+
source,
|
|
85
|
+
requiredAtClosure: lanePaths.externalProviders?.corridor?.requiredAtClosure !== false,
|
|
86
|
+
severityThreshold:
|
|
87
|
+
lanePaths.externalProviders?.corridor?.severityThreshold || DEFAULT_CORRIDOR_SEVERITY_THRESHOLD,
|
|
88
|
+
fetchedAt: new Date().toISOString(),
|
|
89
|
+
relevantOwnedPaths: ownedPaths,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function summarizeCorridorPayload(base, guardrails, findings) {
|
|
94
|
+
const thresholdRank = SEVERITY_RANK[String(base.severityThreshold || "critical").toLowerCase()] || 4;
|
|
95
|
+
const matchedFindings = (Array.isArray(findings) ? findings : [])
|
|
96
|
+
.map((finding) => {
|
|
97
|
+
const matchedOwnedPaths = base.relevantOwnedPaths.filter((ownedPath) =>
|
|
98
|
+
matchesOwnedPath(finding.affectedFile, ownedPath),
|
|
99
|
+
);
|
|
100
|
+
if (matchedOwnedPaths.length === 0) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
id: finding.id || null,
|
|
105
|
+
title: finding.title || null,
|
|
106
|
+
affectedFile: finding.affectedFile || null,
|
|
107
|
+
cwe: finding.cwe || null,
|
|
108
|
+
severity: finding.severity || null,
|
|
109
|
+
state: finding.state || null,
|
|
110
|
+
createdAt: finding.createdAt || null,
|
|
111
|
+
matchedOwnedPaths,
|
|
112
|
+
};
|
|
113
|
+
})
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
const blockingFindings = matchedFindings.filter((finding) => {
|
|
116
|
+
const rank = SEVERITY_RANK[String(finding.severity || "").toLowerCase()] || 0;
|
|
117
|
+
return rank >= thresholdRank;
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
...base,
|
|
121
|
+
ok: true,
|
|
122
|
+
guardrails: Array.isArray(guardrails?.reports) ? guardrails.reports : [],
|
|
123
|
+
matchedFindings,
|
|
124
|
+
blockingFindings,
|
|
125
|
+
blocking: blockingFindings.length > 0,
|
|
126
|
+
error: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function failureCorridorPayload(base, error) {
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
ok: false,
|
|
134
|
+
guardrails: [],
|
|
135
|
+
matchedFindings: [],
|
|
136
|
+
blockingFindings: [],
|
|
137
|
+
blocking: base.requiredAtClosure === true,
|
|
138
|
+
error: error instanceof Error ? error.message : String(error),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function requestCorridorJson(fetchImpl, url, token) {
|
|
143
|
+
const response = await fetchImpl(url, {
|
|
144
|
+
method: "GET",
|
|
145
|
+
headers: {
|
|
146
|
+
authorization: `Bearer ${token}`,
|
|
147
|
+
accept: "application/json",
|
|
148
|
+
},
|
|
149
|
+
signal: AbortSignal.timeout(10000),
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
const payload = await readJsonResponse(response, null);
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Corridor request failed (${response.status}): ${payload?.error || payload?.message || response.statusText || "unknown error"}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return response.json();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function listCorridorFindings(fetchImpl, baseUrl, projectId, token, findingStates) {
|
|
161
|
+
const findings = [];
|
|
162
|
+
const states = findingStates.size > 0 ? [...findingStates] : [null];
|
|
163
|
+
for (const state of states) {
|
|
164
|
+
let nextUrl = new URL(`${baseUrl}/projects/${projectId}/findings`);
|
|
165
|
+
if (state) {
|
|
166
|
+
nextUrl.searchParams.set("state", state);
|
|
167
|
+
}
|
|
168
|
+
let pages = 0;
|
|
169
|
+
while (nextUrl && pages < 10) {
|
|
170
|
+
const payload = await requestCorridorJson(fetchImpl, nextUrl, token);
|
|
171
|
+
if (Array.isArray(payload)) {
|
|
172
|
+
findings.push(...payload);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
const items = Array.isArray(payload?.items)
|
|
176
|
+
? payload.items
|
|
177
|
+
: Array.isArray(payload?.findings)
|
|
178
|
+
? payload.findings
|
|
179
|
+
: Array.isArray(payload?.data)
|
|
180
|
+
? payload.data
|
|
181
|
+
: [];
|
|
182
|
+
findings.push(...items);
|
|
183
|
+
if (payload?.nextPageUrl) {
|
|
184
|
+
nextUrl = new URL(payload.nextPageUrl);
|
|
185
|
+
} else if (payload?.nextCursor) {
|
|
186
|
+
nextUrl = new URL(`${baseUrl}/projects/${projectId}/findings`);
|
|
187
|
+
if (state) {
|
|
188
|
+
nextUrl.searchParams.set("state", state);
|
|
189
|
+
}
|
|
190
|
+
nextUrl.searchParams.set("cursor", String(payload.nextCursor));
|
|
191
|
+
} else if (payload?.page && payload?.totalPages && Number(payload.page) < Number(payload.totalPages)) {
|
|
192
|
+
nextUrl = new URL(`${baseUrl}/projects/${projectId}/findings`);
|
|
193
|
+
if (state) {
|
|
194
|
+
nextUrl.searchParams.set("state", state);
|
|
195
|
+
}
|
|
196
|
+
nextUrl.searchParams.set("page", String(Number(payload.page) + 1));
|
|
197
|
+
} else {
|
|
198
|
+
nextUrl = null;
|
|
199
|
+
}
|
|
200
|
+
pages += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return findings;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function fetchCorridorDirect(fetchImpl, lanePaths, ownedPaths) {
|
|
207
|
+
const corridor = lanePaths.externalProviders?.corridor || {};
|
|
208
|
+
const token =
|
|
209
|
+
process.env[corridor.apiTokenEnvVar || "CORRIDOR_API_TOKEN"] ||
|
|
210
|
+
process.env[corridor.apiKeyFallbackEnvVar || "CORRIDOR_API_KEY"] ||
|
|
211
|
+
"";
|
|
212
|
+
if (!token) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Corridor token is missing; set ${corridor.apiTokenEnvVar || "CORRIDOR_API_TOKEN"} or ${corridor.apiKeyFallbackEnvVar || "CORRIDOR_API_KEY"}.`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const baseUrl = String(corridor.baseUrl || DEFAULT_CORRIDOR_BASE_URL).replace(/\/$/, "");
|
|
218
|
+
const findingStates = new Set((corridor.findingStates || []).map((state) => String(state).trim().toLowerCase()));
|
|
219
|
+
const [guardrails, findings] = await Promise.all([
|
|
220
|
+
requestCorridorJson(fetchImpl, `${baseUrl}/projects/${corridor.projectId}/reports`, token),
|
|
221
|
+
listCorridorFindings(fetchImpl, baseUrl, corridor.projectId, token, findingStates),
|
|
222
|
+
]);
|
|
223
|
+
const filteredFindings = (Array.isArray(findings) ? findings : []).filter((finding) =>
|
|
224
|
+
findingStates.size === 0 || findingStates.has(String(finding.state || "").trim().toLowerCase()),
|
|
225
|
+
);
|
|
226
|
+
return summarizeCorridorPayload(
|
|
227
|
+
corridorArtifactBase({
|
|
228
|
+
lanePaths,
|
|
229
|
+
wave: null,
|
|
230
|
+
ownedPaths,
|
|
231
|
+
providerMode: "direct",
|
|
232
|
+
source: "corridor-api",
|
|
233
|
+
}),
|
|
234
|
+
guardrails,
|
|
235
|
+
filteredFindings,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function fetchCorridorBroker(fetchImpl, lanePaths, waveNumber, ownedPaths) {
|
|
240
|
+
const waveControl = lanePaths.waveControl || {};
|
|
241
|
+
const endpoint = String(waveControl.endpoint || DEFAULT_WAVE_CONTROL_ENDPOINT).trim();
|
|
242
|
+
if (!endpoint || isDefaultWaveControlEndpoint(endpoint)) {
|
|
243
|
+
throw new Error("Corridor broker mode requires an owned Wave Control endpoint.");
|
|
244
|
+
}
|
|
245
|
+
const authToken = resolveWaveControlAuthToken(waveControl);
|
|
246
|
+
if (!authToken) {
|
|
247
|
+
throw new Error("WAVE_API_TOKEN is not set; Corridor broker mode is unavailable.");
|
|
248
|
+
}
|
|
249
|
+
const response = await fetchImpl(`${endpoint.replace(/\/$/, "")}/providers/corridor/context`, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
headers: {
|
|
252
|
+
authorization: `Bearer ${authToken}`,
|
|
253
|
+
"content-type": "application/json",
|
|
254
|
+
accept: "application/json",
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
projectId: lanePaths.project,
|
|
258
|
+
wave: waveNumber,
|
|
259
|
+
ownedPaths,
|
|
260
|
+
severityThreshold:
|
|
261
|
+
lanePaths.externalProviders?.corridor?.severityThreshold || DEFAULT_CORRIDOR_SEVERITY_THRESHOLD,
|
|
262
|
+
findingStates: lanePaths.externalProviders?.corridor?.findingStates || [],
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
const payload = await readJsonResponse(response, null);
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Corridor broker request failed (${response.status}): ${payload?.error || payload?.message || response.statusText || "unknown error"}`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return response.json();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function materializeWaveCorridorContext(
|
|
275
|
+
lanePaths,
|
|
276
|
+
waveDefinition,
|
|
277
|
+
{
|
|
278
|
+
fetchImpl = globalThis.fetch,
|
|
279
|
+
} = {},
|
|
280
|
+
) {
|
|
281
|
+
const corridor = lanePaths.externalProviders?.corridor || {};
|
|
282
|
+
const waveNumber = waveDefinition?.wave ?? 0;
|
|
283
|
+
const artifactPath = waveCorridorContextPath(lanePaths, waveNumber);
|
|
284
|
+
if (!corridor.enabled) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const ownedPaths = (Array.isArray(waveDefinition?.agents) ? waveDefinition.agents : [])
|
|
288
|
+
.filter((agent) => shouldIncludeImplementationOwnedPaths(agent, lanePaths))
|
|
289
|
+
.flatMap((agent) => (Array.isArray(agent.ownedPaths) ? agent.ownedPaths : []))
|
|
290
|
+
.map(normalizeOwnedPath)
|
|
291
|
+
.filter(isRelevantOwnedPath);
|
|
292
|
+
const base = corridorArtifactBase({
|
|
293
|
+
lanePaths,
|
|
294
|
+
wave: waveNumber,
|
|
295
|
+
ownedPaths,
|
|
296
|
+
providerMode: corridor.mode || "direct",
|
|
297
|
+
source: null,
|
|
298
|
+
});
|
|
299
|
+
if (ownedPaths.length === 0) {
|
|
300
|
+
const payload = {
|
|
301
|
+
...base,
|
|
302
|
+
ok: true,
|
|
303
|
+
guardrails: [],
|
|
304
|
+
matchedFindings: [],
|
|
305
|
+
blockingFindings: [],
|
|
306
|
+
blocking: false,
|
|
307
|
+
error: null,
|
|
308
|
+
detail: "No implementation-owned paths were eligible for Corridor matching in this wave.",
|
|
309
|
+
};
|
|
310
|
+
writeJsonAtomic(artifactPath, payload);
|
|
311
|
+
return payload;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
let payload;
|
|
315
|
+
if (corridor.mode === "broker") {
|
|
316
|
+
payload = await fetchCorridorBroker(fetchImpl, lanePaths, waveNumber, ownedPaths);
|
|
317
|
+
} else if (corridor.mode === "hybrid") {
|
|
318
|
+
try {
|
|
319
|
+
payload = await fetchCorridorBroker(fetchImpl, lanePaths, waveNumber, ownedPaths);
|
|
320
|
+
} catch {
|
|
321
|
+
payload = await fetchCorridorDirect(fetchImpl, lanePaths, ownedPaths);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
payload = await fetchCorridorDirect(fetchImpl, lanePaths, ownedPaths);
|
|
325
|
+
}
|
|
326
|
+
const mergedPayload = {
|
|
327
|
+
...base,
|
|
328
|
+
...payload,
|
|
329
|
+
wave: waveNumber,
|
|
330
|
+
lane: lanePaths.lane,
|
|
331
|
+
projectId: lanePaths.project,
|
|
332
|
+
relevantOwnedPaths: ownedPaths,
|
|
333
|
+
requiredAtClosure: corridor.requiredAtClosure !== false,
|
|
334
|
+
};
|
|
335
|
+
writeJsonAtomic(artifactPath, mergedPayload);
|
|
336
|
+
return mergedPayload;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
const payload = failureCorridorPayload(base, error);
|
|
339
|
+
writeJsonAtomic(artifactPath, payload);
|
|
340
|
+
return payload;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function renderCorridorPromptContext(corridorContext) {
|
|
345
|
+
if (!corridorContext || corridorContext.ok !== true) {
|
|
346
|
+
if (corridorContext?.error) {
|
|
347
|
+
return `Corridor provider fetch failed: ${corridorContext.error}`;
|
|
348
|
+
}
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
const lines = [
|
|
352
|
+
`Corridor source: ${corridorContext.source || corridorContext.providerMode || "unknown"}`,
|
|
353
|
+
`Corridor blocking: ${corridorContext.blocking ? "yes" : "no"}`,
|
|
354
|
+
`Corridor threshold: ${corridorContext.severityThreshold || DEFAULT_CORRIDOR_SEVERITY_THRESHOLD}`,
|
|
355
|
+
`Corridor matched findings: ${(corridorContext.matchedFindings || []).length}`,
|
|
356
|
+
];
|
|
357
|
+
for (const finding of (corridorContext.blockingFindings || []).slice(0, 5)) {
|
|
358
|
+
lines.push(
|
|
359
|
+
`- ${finding.severity || "unknown"} ${finding.affectedFile || "unknown-file"}: ${finding.title || "finding"}`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
@@ -47,6 +47,10 @@ import {
|
|
|
47
47
|
describeContext7Libraries,
|
|
48
48
|
loadContext7BundleIndex,
|
|
49
49
|
} from "./context7.mjs";
|
|
50
|
+
import {
|
|
51
|
+
readWaveCorridorContext,
|
|
52
|
+
waveCorridorContextPath,
|
|
53
|
+
} from "./corridor.mjs";
|
|
50
54
|
|
|
51
55
|
export function waveCoordinationLogPath(lanePaths, waveNumber) {
|
|
52
56
|
return path.join(lanePaths.coordinationDir, `wave-${waveNumber}.jsonl`);
|
|
@@ -212,6 +216,7 @@ export function buildWaveSecuritySummary({
|
|
|
212
216
|
wave,
|
|
213
217
|
attempt,
|
|
214
218
|
summariesByAgentId = {},
|
|
219
|
+
corridorSummary = null,
|
|
215
220
|
}) {
|
|
216
221
|
const createdAt = toIsoTimestamp();
|
|
217
222
|
const securityAgents = (wave.agents || []).filter((agent) =>
|
|
@@ -275,7 +280,9 @@ export function buildWaveSecuritySummary({
|
|
|
275
280
|
const totalFindings = agents.reduce((sum, entry) => sum + (entry.findings || 0), 0);
|
|
276
281
|
const totalApprovals = agents.reduce((sum, entry) => sum + (entry.approvals || 0), 0);
|
|
277
282
|
const detail =
|
|
278
|
-
|
|
283
|
+
corridorSummary?.blocking
|
|
284
|
+
? `Corridor matched blocking findings for implementation-owned paths.`
|
|
285
|
+
: overallState === "blocked"
|
|
279
286
|
? `Security review blocked by ${blockedAgentIds.join(", ")}.`
|
|
280
287
|
: overallState === "pending"
|
|
281
288
|
? `Security review output is incomplete for ${pendingAgentIds.join(", ")}.`
|
|
@@ -292,6 +299,17 @@ export function buildWaveSecuritySummary({
|
|
|
292
299
|
concernAgentIds,
|
|
293
300
|
blockedAgentIds,
|
|
294
301
|
detail,
|
|
302
|
+
corridor: corridorSummary
|
|
303
|
+
? {
|
|
304
|
+
ok: corridorSummary.ok === true,
|
|
305
|
+
providerMode: corridorSummary.providerMode || null,
|
|
306
|
+
source: corridorSummary.source || null,
|
|
307
|
+
blocking: corridorSummary.blocking === true,
|
|
308
|
+
blockingFindings: corridorSummary.blockingFindings || [],
|
|
309
|
+
matchedFindings: corridorSummary.matchedFindings || [],
|
|
310
|
+
error: corridorSummary.error || null,
|
|
311
|
+
}
|
|
312
|
+
: null,
|
|
295
313
|
agents,
|
|
296
314
|
createdAt,
|
|
297
315
|
updatedAt: createdAt,
|
|
@@ -492,6 +510,15 @@ function buildIntegrationEvidence({
|
|
|
492
510
|
);
|
|
493
511
|
}
|
|
494
512
|
}
|
|
513
|
+
for (const finding of securitySummary?.corridor?.matchedFindings || []) {
|
|
514
|
+
securityFindingEntries.push(
|
|
515
|
+
summarizeGap(
|
|
516
|
+
"corridor",
|
|
517
|
+
`${finding.severity || "unknown"} ${finding.affectedFile || "unknown-file"}: ${finding.title || "finding"}`,
|
|
518
|
+
"Corridor matched a relevant finding.",
|
|
519
|
+
),
|
|
520
|
+
);
|
|
521
|
+
}
|
|
495
522
|
|
|
496
523
|
return {
|
|
497
524
|
openClaims: uniqueStringEntries(openClaims),
|
|
@@ -508,6 +535,13 @@ function buildIntegrationEvidence({
|
|
|
508
535
|
securityState: securitySummary?.overallState || "not-applicable",
|
|
509
536
|
securityFindings: uniqueStringEntries(securityFindingEntries),
|
|
510
537
|
securityApprovals: uniqueStringEntries(securityApprovalEntries),
|
|
538
|
+
corridorState: securitySummary?.corridor?.blocking
|
|
539
|
+
? "blocked"
|
|
540
|
+
: securitySummary?.corridor?.ok === false
|
|
541
|
+
? "error"
|
|
542
|
+
: securitySummary?.corridor
|
|
543
|
+
? "clear"
|
|
544
|
+
: "not-configured",
|
|
511
545
|
};
|
|
512
546
|
}
|
|
513
547
|
|
|
@@ -683,6 +717,7 @@ export function buildWaveDerivedState({
|
|
|
683
717
|
wave,
|
|
684
718
|
attempt,
|
|
685
719
|
summariesByAgentId,
|
|
720
|
+
corridorSummary: readWaveCorridorContext(lanePaths, wave.wave),
|
|
686
721
|
});
|
|
687
722
|
const integrationSummary = buildWaveIntegrationSummary({
|
|
688
723
|
lanePaths,
|
|
@@ -762,6 +797,8 @@ export function buildWaveDerivedState({
|
|
|
762
797
|
dependencySnapshotMarkdownPath: waveDependencySnapshotMarkdownPath(lanePaths, wave.wave),
|
|
763
798
|
securitySummary,
|
|
764
799
|
securitySummaryPath: waveSecurityPath(lanePaths, wave.wave),
|
|
800
|
+
corridorSummary: readWaveCorridorContext(lanePaths, wave.wave),
|
|
801
|
+
corridorSummaryPath: waveCorridorContextPath(lanePaths, wave.wave),
|
|
765
802
|
integrationSummary,
|
|
766
803
|
integrationSummaryPath: waveIntegrationPath(lanePaths, wave.wave),
|
|
767
804
|
integrationMarkdownPath: waveIntegrationMarkdownPath(lanePaths, wave.wave),
|
|
@@ -1414,6 +1414,25 @@ export function readWaveSecurityGatePure(wave, agentResults, options = {}) {
|
|
|
1414
1414
|
const securityAgents = agents.filter((agent) =>
|
|
1415
1415
|
isSecurityReviewAgentWithOptions(agent, options),
|
|
1416
1416
|
);
|
|
1417
|
+
const corridorSummary = options.derivedState?.corridorSummary || null;
|
|
1418
|
+
if (corridorSummary?.ok === false && corridorSummary?.requiredAtClosure !== false) {
|
|
1419
|
+
return {
|
|
1420
|
+
ok: false,
|
|
1421
|
+
agentId: null,
|
|
1422
|
+
statusCode: "corridor-fetch-failed",
|
|
1423
|
+
detail: corridorSummary.error || "Corridor context fetch failed.",
|
|
1424
|
+
logPath: null,
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
if (corridorSummary?.blocking) {
|
|
1428
|
+
return {
|
|
1429
|
+
ok: false,
|
|
1430
|
+
agentId: null,
|
|
1431
|
+
statusCode: "corridor-blocked",
|
|
1432
|
+
detail: `Corridor matched ${corridorSummary.blockingFindings?.length || 0} blocking finding(s) on implementation-owned paths.`,
|
|
1433
|
+
logPath: null,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1417
1436
|
if (securityAgents.length === 0) {
|
|
1418
1437
|
return { ok: true, agentId: null, statusCode: "pass",
|
|
1419
1438
|
detail: "No security reviewer declared for this wave.", logPath: null };
|
|
@@ -1526,6 +1545,7 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
|
|
|
1526
1545
|
contEvalAgentId: laneConfig.contEvalAgentId, mode: validationMode,
|
|
1527
1546
|
evalTargets: wave.evalTargets, benchmarkCatalogPath: laneConfig.benchmarkCatalogPath });
|
|
1528
1547
|
const securityGate = readWaveSecurityGatePure(wave, agentResults, {
|
|
1548
|
+
derivedState,
|
|
1529
1549
|
securityRolePromptPath: laneConfig.securityRolePromptPath,
|
|
1530
1550
|
});
|
|
1531
1551
|
const contQaGate = readWaveContQaGatePure(wave, agentResults, {
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
WAVE_PACKAGE_NAME,
|
|
17
17
|
} from "./package-version.mjs";
|
|
18
18
|
import { loadWaveConfig } from "./config.mjs";
|
|
19
|
+
import {
|
|
20
|
+
isDefaultWaveControlEndpoint,
|
|
21
|
+
resolveWaveControlAuthToken,
|
|
22
|
+
} from "./provider-runtime.mjs";
|
|
19
23
|
import { applyExecutorSelectionsToWave, parseWaveFiles, validateWaveDefinition } from "./wave-files.mjs";
|
|
20
24
|
import { validateLaneSkillConfiguration } from "./skills.mjs";
|
|
21
25
|
|
|
@@ -65,7 +69,7 @@ export const STARTER_TEMPLATE_PATHS = [
|
|
|
65
69
|
"docs/guides/author-and-run-waves.md",
|
|
66
70
|
"docs/guides/monorepo-projects.md",
|
|
67
71
|
"docs/guides/planner.md",
|
|
68
|
-
"docs/guides/recommendations-0.9.
|
|
72
|
+
"docs/guides/recommendations-0.9.3.md",
|
|
69
73
|
"docs/guides/sandboxed-environments.md",
|
|
70
74
|
"docs/guides/signal-wrappers.md",
|
|
71
75
|
"docs/guides/terminal-surfaces.md",
|
|
@@ -82,6 +86,8 @@ export const STARTER_TEMPLATE_PATHS = [
|
|
|
82
86
|
"docs/plans/wave-orchestrator.md",
|
|
83
87
|
"docs/plans/waves/wave-0.md",
|
|
84
88
|
"docs/reference/cli-reference.md",
|
|
89
|
+
"docs/reference/coordination-and-closure.md",
|
|
90
|
+
"docs/reference/corridor.md",
|
|
85
91
|
"docs/reference/live-proof-waves.md",
|
|
86
92
|
"docs/reference/npmjs-token-publishing.md",
|
|
87
93
|
"docs/reference/npmjs-trusted-publishing.md",
|
|
@@ -89,6 +95,7 @@ export const STARTER_TEMPLATE_PATHS = [
|
|
|
89
95
|
"docs/reference/repository-guidance.md",
|
|
90
96
|
"docs/reference/sample-waves.md",
|
|
91
97
|
"docs/reference/skills.md",
|
|
98
|
+
"docs/reference/wave-control.md",
|
|
92
99
|
"docs/reference/wave-planning-lessons.md",
|
|
93
100
|
"skills/role-design/SKILL.md",
|
|
94
101
|
"skills/role-design/skill.json",
|
|
@@ -421,6 +428,32 @@ export function runDoctor() {
|
|
|
421
428
|
if (config) {
|
|
422
429
|
try {
|
|
423
430
|
const lanePaths = buildLanePaths(config.defaultLane, { config });
|
|
431
|
+
const context7Mode = lanePaths.externalProviders?.context7?.mode || "direct";
|
|
432
|
+
const corridor = lanePaths.externalProviders?.corridor || {};
|
|
433
|
+
const corridorMode = corridor.mode || "direct";
|
|
434
|
+
const usesBroker =
|
|
435
|
+
context7Mode === "broker" ||
|
|
436
|
+
context7Mode === "hybrid" ||
|
|
437
|
+
corridorMode === "broker" ||
|
|
438
|
+
corridorMode === "hybrid";
|
|
439
|
+
if (usesBroker && isDefaultWaveControlEndpoint(lanePaths.waveControl?.endpoint)) {
|
|
440
|
+
const message =
|
|
441
|
+
"Brokered external providers require an owned Wave Control endpoint; the packaged default endpoint must not be used as a shared broker.";
|
|
442
|
+
if (context7Mode === "broker" || corridorMode === "broker") {
|
|
443
|
+
errors.push(message);
|
|
444
|
+
} else {
|
|
445
|
+
warnings.push(message);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (usesBroker && !resolveWaveControlAuthToken(lanePaths.waveControl)) {
|
|
449
|
+
const message =
|
|
450
|
+
"Brokered external providers require a Wave Control auth token. Set WAVE_API_TOKEN or the configured legacy auth token env var.";
|
|
451
|
+
if (context7Mode === "broker" || corridorMode === "broker") {
|
|
452
|
+
errors.push(message);
|
|
453
|
+
} else {
|
|
454
|
+
warnings.push(message);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
424
457
|
if (!fs.existsSync(path.join(REPO_ROOT, "wave.config.json"))) {
|
|
425
458
|
errors.push("Missing wave.config.json.");
|
|
426
459
|
}
|