@lsctech/polaris 0.5.6 → 0.5.7
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/dist/autoresearch/proposal.js +5 -0
- package/dist/autoresearch/score.js +174 -2
- package/dist/autoresearch/sol-evidence-loader.js +40 -3
- package/dist/autoresearch/sol-recommendations.js +82 -0
- package/dist/autoresearch/sol-report.js +44 -0
- package/dist/autoresearch/sol-scorer.js +45 -1
- package/dist/config/defaults.js +1 -0
- package/dist/config/validator.js +157 -0
- package/dist/finalize/index.js +1 -1
- package/dist/loop/parent.js +156 -5
- package/dist/loop/worker-packet.js +75 -1
- package/dist/qc/fixtures/repair-packets.js +170 -0
- package/dist/qc/index.js +2 -0
- package/dist/qc/orchestration.js +2 -0
- package/dist/qc/policy.js +30 -3
- package/dist/qc/providers/coderabbit.js +39 -7
- package/dist/qc/registry.js +36 -3
- package/dist/qc/repair-loop.js +423 -0
- package/dist/qc/repair-packets.js +420 -0
- package/dist/qc/runner.js +320 -37
- package/dist/qc/schemas.js +78 -1
- package/package.json +1 -1
package/dist/qc/runner.js
CHANGED
|
@@ -2,15 +2,29 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* QC provider execution runner.
|
|
4
4
|
*
|
|
5
|
-
* Runs a provider-specific review command,
|
|
6
|
-
* the
|
|
5
|
+
* Runs a provider-specific review command, classifies non-finding failures, and
|
|
6
|
+
* normalizes the outcome into a Polaris QcResult with a durable providerAttempt
|
|
7
|
+
* record. Supports optional fallback chains driven by per-provider failure policy.
|
|
7
8
|
*/
|
|
8
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
10
|
exports.executeQcProvider = executeQcProvider;
|
|
10
11
|
const node_child_process_1 = require("node:child_process");
|
|
11
12
|
const node_fs_1 = require("node:fs");
|
|
12
13
|
const node_path_1 = require("node:path");
|
|
13
|
-
|
|
14
|
+
const policy_js_1 = require("./policy.js");
|
|
15
|
+
function makeProviderAttempt(providerName, status, output, overrides) {
|
|
16
|
+
return {
|
|
17
|
+
provider: providerName,
|
|
18
|
+
status,
|
|
19
|
+
rawOutputAvailable: (output.stdout?.length ?? 0) > 0 || (output.stderr?.length ?? 0) > 0,
|
|
20
|
+
rawOutputRetained: false,
|
|
21
|
+
stdoutLength: output.stdout?.length ?? 0,
|
|
22
|
+
stderrLength: output.stderr?.length ?? 0,
|
|
23
|
+
exitCode: output.exitCode,
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function makeSyntheticResult(provider, scope, startedAt, status, summary, providerAttempt) {
|
|
14
28
|
const completedAt = new Date().toISOString();
|
|
15
29
|
return {
|
|
16
30
|
schemaVersion: "1.0",
|
|
@@ -28,11 +42,12 @@ function makeSyntheticResult(provider, scope, startedAt, status, summary) {
|
|
|
28
42
|
rawArtifactPaths: [],
|
|
29
43
|
parserVersion: `${provider.name}-synthetic`,
|
|
30
44
|
policyDecision: {
|
|
31
|
-
blocksDelivery:
|
|
32
|
-
requiresOperatorReview: status !== "passed",
|
|
45
|
+
blocksDelivery: status === "blocked" || status === "failed",
|
|
46
|
+
requiresOperatorReview: status !== "passed" && status !== "skipped",
|
|
33
47
|
routedToRepair: false,
|
|
34
48
|
summary,
|
|
35
49
|
},
|
|
50
|
+
providerAttempt,
|
|
36
51
|
};
|
|
37
52
|
}
|
|
38
53
|
function appendTelemetry(telemetryFile, event) {
|
|
@@ -46,30 +61,131 @@ function appendTelemetry(telemetryFile, event) {
|
|
|
46
61
|
// Telemetry is best-effort.
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
function resolveMode(scope) {
|
|
65
|
+
return scope.prUrl ? "pr" : "local";
|
|
66
|
+
}
|
|
67
|
+
function isModeSupported(provider, scope) {
|
|
68
|
+
const mode = resolveMode(scope);
|
|
69
|
+
return provider.supportedModes.includes(mode);
|
|
70
|
+
}
|
|
71
|
+
function getProviderConfig(name, config) {
|
|
72
|
+
return config?.providers?.[name];
|
|
73
|
+
}
|
|
74
|
+
function classifyTerminalFailure(error, output) {
|
|
75
|
+
const execError = error;
|
|
76
|
+
if (execError?.killed && execError?.signal === "SIGTERM") {
|
|
77
|
+
return "timeout";
|
|
78
|
+
}
|
|
79
|
+
if (execError?.code === "ENOENT") {
|
|
80
|
+
return "command-not-found";
|
|
81
|
+
}
|
|
82
|
+
// Only scan output for failure keywords if the command actually failed
|
|
83
|
+
if (!error && output.exitCode === 0) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const text = `${output.stdout ?? ""}\n${output.stderr ?? ""}`.toLowerCase();
|
|
87
|
+
if (text.includes("rate limit") ||
|
|
88
|
+
text.includes("429") ||
|
|
89
|
+
text.includes("too many requests")) {
|
|
90
|
+
return "rate-limited";
|
|
91
|
+
}
|
|
92
|
+
if (text.includes("unauthorized") ||
|
|
93
|
+
text.includes("authentication failed") ||
|
|
94
|
+
text.includes("invalid token") ||
|
|
95
|
+
text.includes("401") ||
|
|
96
|
+
text.includes("forbidden") ||
|
|
97
|
+
text.includes("403")) {
|
|
98
|
+
return "auth-failure";
|
|
99
|
+
}
|
|
100
|
+
if (text.includes("unavailable") ||
|
|
101
|
+
text.includes("service unavailable") ||
|
|
102
|
+
text.includes("503") ||
|
|
103
|
+
text.includes("connection refused") ||
|
|
104
|
+
text.includes("econnrefused")) {
|
|
105
|
+
return "unavailable-provider";
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
function classifyPostParseFailure(output, parsed) {
|
|
110
|
+
if (parsed.findings.length > 0) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const stdout = output.stdout ?? "";
|
|
114
|
+
const stderr = output.stderr ?? "";
|
|
115
|
+
if (stdout.trim().length === 0 && stderr.trim().length === 0) {
|
|
116
|
+
if (output.exitCode !== 0) {
|
|
117
|
+
return "empty-output";
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
if (output.exitCode !== 0) {
|
|
122
|
+
return "nonzero-exit";
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
function emitProviderAttempted(telemetryFile, runId, clusterId, providerName, fallbackSource) {
|
|
127
|
+
appendTelemetry(telemetryFile, {
|
|
128
|
+
event: fallbackSource ? "qc-fallback-attempted" : "qc-provider-attempted",
|
|
129
|
+
run_id: runId,
|
|
130
|
+
cluster_id: clusterId,
|
|
131
|
+
provider: providerName,
|
|
132
|
+
fallback_source: fallbackSource,
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function emitProviderFailed(telemetryFile, runId, clusterId, providerName, reason, exitCode) {
|
|
137
|
+
appendTelemetry(telemetryFile, {
|
|
138
|
+
event: "qc-provider-failed",
|
|
139
|
+
run_id: runId,
|
|
140
|
+
cluster_id: clusterId,
|
|
141
|
+
provider: providerName,
|
|
57
142
|
reason,
|
|
58
|
-
|
|
143
|
+
exit_code: exitCode,
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
59
145
|
});
|
|
60
|
-
return result;
|
|
61
146
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
147
|
+
function emitFallbackSucceeded(telemetryFile, runId, clusterId, providerName, fallbackSource) {
|
|
148
|
+
appendTelemetry(telemetryFile, {
|
|
149
|
+
event: "qc-fallback-succeeded",
|
|
150
|
+
run_id: runId,
|
|
151
|
+
cluster_id: clusterId,
|
|
152
|
+
provider: providerName,
|
|
153
|
+
fallback_source: fallbackSource,
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function emitAllProvidersFailed(telemetryFile, runId, clusterId, attempted) {
|
|
158
|
+
appendTelemetry(telemetryFile, {
|
|
159
|
+
event: "qc-all-providers-failed",
|
|
160
|
+
run_id: runId,
|
|
161
|
+
cluster_id: clusterId,
|
|
162
|
+
attempted_providers: attempted,
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function buildFailedResult(provider, scope, startedAt, reason, output, overrides) {
|
|
167
|
+
const providerAttempt = makeProviderAttempt(provider.name, "failure", output, {
|
|
168
|
+
failureReason: reason,
|
|
169
|
+
...overrides,
|
|
170
|
+
});
|
|
171
|
+
return makeSyntheticResult(provider, scope, startedAt, "failed", `QC provider ${provider.name} failed: ${reason}`, providerAttempt);
|
|
172
|
+
}
|
|
173
|
+
async function runSingleProvider(provider, scope, options, fallbackSource) {
|
|
69
174
|
const startedAt = new Date().toISOString();
|
|
70
175
|
const command = provider.buildReviewCommand(scope);
|
|
71
176
|
const execFn = options.execFileImpl ?? node_child_process_1.execFile;
|
|
72
177
|
const timeoutMs = options.timeoutMs ?? 300_000; // 5 minutes default
|
|
178
|
+
const providerConfig = getProviderConfig(provider.name, options.config);
|
|
179
|
+
emitProviderAttempted(options.telemetryFile, scope.runId, scope.clusterId, provider.name, fallbackSource);
|
|
180
|
+
if (!isModeSupported(provider, scope)) {
|
|
181
|
+
const output = {
|
|
182
|
+
provider: provider.name,
|
|
183
|
+
exitCode: 1,
|
|
184
|
+
};
|
|
185
|
+
const result = buildFailedResult(provider, scope, startedAt, "unsupported-mode", output);
|
|
186
|
+
emitProviderFailed(options.telemetryFile, scope.runId, scope.clusterId, provider.name, "unsupported-mode");
|
|
187
|
+
return { result, success: false };
|
|
188
|
+
}
|
|
73
189
|
return new Promise((resolve) => {
|
|
74
190
|
let stdout = "";
|
|
75
191
|
let stderr = "";
|
|
@@ -81,28 +197,77 @@ async function executeQcProvider(provider, scope, options) {
|
|
|
81
197
|
}, (error, out, err) => {
|
|
82
198
|
stdout = out ?? "";
|
|
83
199
|
stderr = err ?? "";
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
200
|
+
const output = {
|
|
201
|
+
provider: provider.name,
|
|
202
|
+
stdout,
|
|
203
|
+
stderr,
|
|
204
|
+
exitCode: error ? (typeof error.code === "number" ? error.code : 1) : 0,
|
|
205
|
+
};
|
|
206
|
+
const terminalReason = classifyTerminalFailure(error, output);
|
|
207
|
+
if (terminalReason) {
|
|
208
|
+
const result = buildFailedResult(provider, scope, startedAt, terminalReason, output);
|
|
209
|
+
emitProviderFailed(options.telemetryFile, scope.runId, scope.clusterId, provider.name, terminalReason, output.exitCode);
|
|
210
|
+
resolve({ result, success: false });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Empty output combined with a nonzero exit is an explicit failure.
|
|
214
|
+
if (output.exitCode !== 0 && stdout.trim().length === 0 && stderr.trim().length === 0) {
|
|
215
|
+
const result = buildFailedResult(provider, scope, startedAt, "empty-output", output);
|
|
216
|
+
emitProviderFailed(options.telemetryFile, scope.runId, scope.clusterId, provider.name, "empty-output", output.exitCode);
|
|
217
|
+
resolve({ result, success: false });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Empty success output is a valid "no findings" result.
|
|
221
|
+
if (output.exitCode === 0 && stdout.trim().length === 0 && stderr.trim().length === 0) {
|
|
222
|
+
const completedAt = new Date().toISOString();
|
|
223
|
+
const result = {
|
|
224
|
+
schemaVersion: "1.0",
|
|
225
|
+
qcRunId: `${provider.name}-${Date.now()}`,
|
|
226
|
+
runId: scope.runId,
|
|
227
|
+
clusterId: scope.clusterId,
|
|
228
|
+
trigger: scope.prUrl ? "pr" : "completed-cluster",
|
|
229
|
+
provider: provider.name,
|
|
230
|
+
providerMode: scope.prUrl ? "pr" : "local",
|
|
231
|
+
prUrl: scope.prUrl,
|
|
232
|
+
startedAt,
|
|
233
|
+
completedAt,
|
|
234
|
+
status: "passed",
|
|
235
|
+
findings: [],
|
|
236
|
+
rawArtifactPaths: [],
|
|
237
|
+
parserVersion: `${provider.name}-1.0`,
|
|
238
|
+
policyDecision: {
|
|
239
|
+
blocksDelivery: false,
|
|
240
|
+
requiresOperatorReview: false,
|
|
241
|
+
routedToRepair: false,
|
|
242
|
+
summary: "No findings",
|
|
243
|
+
},
|
|
244
|
+
providerAttempt: makeProviderAttempt(provider.name, "success", output, {
|
|
245
|
+
parserResult: "success",
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
87
248
|
appendTelemetry(options.telemetryFile, {
|
|
88
|
-
event: "qc-provider-
|
|
249
|
+
event: "qc-provider-executed",
|
|
89
250
|
run_id: scope.runId,
|
|
90
251
|
cluster_id: scope.clusterId,
|
|
91
252
|
provider: provider.name,
|
|
92
|
-
|
|
93
|
-
|
|
253
|
+
trigger: result.trigger,
|
|
254
|
+
status: result.status,
|
|
255
|
+
findings_count: 0,
|
|
256
|
+
exit_code: 0,
|
|
257
|
+
timestamp: completedAt,
|
|
94
258
|
});
|
|
95
|
-
resolve(result);
|
|
259
|
+
resolve({ result, success: true });
|
|
96
260
|
return;
|
|
97
261
|
}
|
|
98
|
-
const output = {
|
|
99
|
-
provider: provider.name,
|
|
100
|
-
stdout,
|
|
101
|
-
stderr,
|
|
102
|
-
exitCode: error ? (typeof error.code === "number" ? error.code : 1) : 0,
|
|
103
|
-
};
|
|
104
262
|
try {
|
|
105
263
|
const parsed = provider.parse(output);
|
|
264
|
+
const postParseReason = classifyPostParseFailure(output, parsed);
|
|
265
|
+
if (postParseReason) {
|
|
266
|
+
const result = buildFailedResult(provider, scope, startedAt, postParseReason, output);
|
|
267
|
+
emitProviderFailed(options.telemetryFile, scope.runId, scope.clusterId, provider.name, postParseReason, output.exitCode);
|
|
268
|
+
resolve({ result, success: false });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
106
271
|
const completedAt = new Date().toISOString();
|
|
107
272
|
const result = {
|
|
108
273
|
...parsed,
|
|
@@ -115,6 +280,9 @@ async function executeQcProvider(provider, scope, options) {
|
|
|
115
280
|
prUrl: scope.prUrl,
|
|
116
281
|
startedAt,
|
|
117
282
|
completedAt,
|
|
283
|
+
providerAttempt: makeProviderAttempt(provider.name, "success", output, {
|
|
284
|
+
parserResult: "success",
|
|
285
|
+
}),
|
|
118
286
|
};
|
|
119
287
|
appendTelemetry(options.telemetryFile, {
|
|
120
288
|
event: "qc-provider-executed",
|
|
@@ -127,11 +295,126 @@ async function executeQcProvider(provider, scope, options) {
|
|
|
127
295
|
exit_code: output.exitCode,
|
|
128
296
|
timestamp: completedAt,
|
|
129
297
|
});
|
|
130
|
-
resolve(result);
|
|
298
|
+
resolve({ result, success: true });
|
|
131
299
|
}
|
|
132
300
|
catch (parseError) {
|
|
133
|
-
|
|
301
|
+
const result = buildFailedResult(provider, scope, startedAt, "parse-failed", output, { parserResult: "failed" });
|
|
302
|
+
emitProviderFailed(options.telemetryFile, scope.runId, scope.clusterId, provider.name, "parse-failed", output.exitCode);
|
|
303
|
+
resolve({ result, success: false });
|
|
134
304
|
}
|
|
135
305
|
});
|
|
136
306
|
});
|
|
137
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Execute a provider review command for the given scope.
|
|
310
|
+
*
|
|
311
|
+
* Timeouts, parse failures, and other non-finding outcomes are normalized
|
|
312
|
+
* into QcResult objects that carry a providerAttempt classification. When a
|
|
313
|
+
* QcConfig and QcProviderRegistry are supplied, the runner follows each
|
|
314
|
+
* provider's fallback list according to its failurePolicy.
|
|
315
|
+
*/
|
|
316
|
+
async function executeQcProvider(provider, scope, options) {
|
|
317
|
+
const visited = new Set();
|
|
318
|
+
const attempted = [];
|
|
319
|
+
async function attemptChain(currentProvider, fallbackSource) {
|
|
320
|
+
if (visited.has(currentProvider.name)) {
|
|
321
|
+
return {
|
|
322
|
+
result: buildFailedResult(currentProvider, scope, new Date().toISOString(), "unavailable-provider", { provider: currentProvider.name, exitCode: 1 }),
|
|
323
|
+
success: false,
|
|
324
|
+
sourceProvider: currentProvider.name,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
visited.add(currentProvider.name);
|
|
328
|
+
attempted.push(currentProvider.name);
|
|
329
|
+
const { result, success } = await runSingleProvider(currentProvider, scope, options, fallbackSource);
|
|
330
|
+
if (success) {
|
|
331
|
+
return { result, success, sourceProvider: currentProvider.name };
|
|
332
|
+
}
|
|
333
|
+
const providerConfig = getProviderConfig(currentProvider.name, options.config);
|
|
334
|
+
const failureAction = (0, policy_js_1.decideProviderFailureAction)(result.providerAttempt?.failureReason ?? "nonzero-exit", providerConfig?.failurePolicy);
|
|
335
|
+
if (failureAction === "ignore") {
|
|
336
|
+
const skippedResult = {
|
|
337
|
+
...result,
|
|
338
|
+
status: "skipped",
|
|
339
|
+
providerAttempt: result.providerAttempt
|
|
340
|
+
? { ...result.providerAttempt, status: "skipped" }
|
|
341
|
+
: undefined,
|
|
342
|
+
policyDecision: {
|
|
343
|
+
...result.policyDecision,
|
|
344
|
+
blocksDelivery: false,
|
|
345
|
+
requiresOperatorReview: false,
|
|
346
|
+
summary: `QC provider ${currentProvider.name} skipped per failure policy`,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
return { result: skippedResult, success: true, sourceProvider: currentProvider.name };
|
|
350
|
+
}
|
|
351
|
+
if (failureAction === "block") {
|
|
352
|
+
const blockedResult = {
|
|
353
|
+
...result,
|
|
354
|
+
status: "blocked",
|
|
355
|
+
policyDecision: {
|
|
356
|
+
...result.policyDecision,
|
|
357
|
+
blocksDelivery: true,
|
|
358
|
+
summary: `QC provider ${currentProvider.name} blocked per failure policy`,
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
return { result: blockedResult, success: false, sourceProvider: currentProvider.name };
|
|
362
|
+
}
|
|
363
|
+
if (failureAction !== "fallback" || !options.registry || !options.config) {
|
|
364
|
+
return { result, success: false, sourceProvider: currentProvider.name };
|
|
365
|
+
}
|
|
366
|
+
const fallbackNames = providerConfig?.fallback ?? [];
|
|
367
|
+
for (const fallbackName of fallbackNames) {
|
|
368
|
+
const fallbackProvider = options.registry.get(fallbackName);
|
|
369
|
+
if (!fallbackProvider) {
|
|
370
|
+
attempted.push(fallbackName);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const chainResult = await attemptChain(fallbackProvider, currentProvider.name);
|
|
374
|
+
if (chainResult.success) {
|
|
375
|
+
// Only emit telemetry and set fallbackSource if not already set by a deeper level
|
|
376
|
+
const existingFallbackSource = chainResult.result.providerAttempt?.fallbackSource;
|
|
377
|
+
if (!existingFallbackSource) {
|
|
378
|
+
emitFallbackSucceeded(options.telemetryFile, scope.runId, scope.clusterId, chainResult.sourceProvider, currentProvider.name);
|
|
379
|
+
}
|
|
380
|
+
const finalResult = {
|
|
381
|
+
...chainResult.result,
|
|
382
|
+
providerAttempt: chainResult.result.providerAttempt
|
|
383
|
+
? {
|
|
384
|
+
...chainResult.result.providerAttempt,
|
|
385
|
+
fallbackSource: existingFallbackSource ?? currentProvider.name,
|
|
386
|
+
}
|
|
387
|
+
: undefined,
|
|
388
|
+
};
|
|
389
|
+
return { result: finalResult, success: true, sourceProvider: chainResult.sourceProvider };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { result, success: false, sourceProvider: currentProvider.name };
|
|
393
|
+
}
|
|
394
|
+
const { result, success, sourceProvider } = await attemptChain(provider);
|
|
395
|
+
if (success) {
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
// If the primary provider (or its fallback chain) failed and no fallback
|
|
399
|
+
// succeeded, emit an all-providers-failed result and telemetry event when a
|
|
400
|
+
// registry/config was supplied so the chain was actually resolvable.
|
|
401
|
+
if (options.registry && options.config) {
|
|
402
|
+
emitAllProvidersFailed(options.telemetryFile, scope.runId, scope.clusterId, attempted);
|
|
403
|
+
const allFailedResult = {
|
|
404
|
+
...result,
|
|
405
|
+
status: "failed",
|
|
406
|
+
allProvidersFailed: true,
|
|
407
|
+
providerAttempt: result.providerAttempt
|
|
408
|
+
? { ...result.providerAttempt, status: "failure" }
|
|
409
|
+
: undefined,
|
|
410
|
+
policyDecision: {
|
|
411
|
+
...result.policyDecision,
|
|
412
|
+
blocksDelivery: true,
|
|
413
|
+
requiresOperatorReview: true,
|
|
414
|
+
summary: `All QC providers failed for ${provider.name}`,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
return allFailedResult;
|
|
418
|
+
}
|
|
419
|
+
return result;
|
|
420
|
+
}
|
package/dist/qc/schemas.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.qcResultSchema = exports.qcPolicyDecisionSchema = exports.qcFindingSchema = exports.qcAttributionSchema = exports.qcCodeRangeSchema = exports.qcAttributionReasonSchema = exports.qcAttributionConfidenceSchema = exports.qcRoutingDecisionSchema = exports.qcFindingStatusSchema = exports.qcSeveritySchema = void 0;
|
|
3
|
+
exports.qcRepairPacketManifestSchema = exports.qcRepairPacketSchema = exports.qcRepairPacketStatusSchema = exports.qcResultSchema = exports.qcPolicyDecisionSchema = exports.qcFindingSchema = exports.qcAttributionSchema = exports.qcCodeRangeSchema = exports.qcProviderAttemptSchema = exports.qcProviderAttemptStatusSchema = exports.qcParserResultSchema = exports.qcFailureReasonSchema = exports.qcAttributionReasonSchema = exports.qcAttributionConfidenceSchema = exports.qcRoutingDecisionSchema = exports.qcFindingStatusSchema = exports.qcSeveritySchema = void 0;
|
|
4
4
|
exports.validateQcResult = validateQcResult;
|
|
5
5
|
exports.validateQcFinding = validateQcFinding;
|
|
6
|
+
exports.validateRepairPacketManifest = validateRepairPacketManifest;
|
|
6
7
|
const zod_1 = require("zod");
|
|
7
8
|
exports.qcSeveritySchema = zod_1.z.enum(["critical", "high", "medium", "low", "info"]);
|
|
8
9
|
exports.qcFindingStatusSchema = zod_1.z.enum([
|
|
@@ -33,6 +34,37 @@ exports.qcAttributionReasonSchema = zod_1.z.enum([
|
|
|
33
34
|
"provider-uncertain",
|
|
34
35
|
"unattributed",
|
|
35
36
|
]);
|
|
37
|
+
exports.qcFailureReasonSchema = zod_1.z.enum([
|
|
38
|
+
"timeout",
|
|
39
|
+
"rate-limited",
|
|
40
|
+
"auth-failure",
|
|
41
|
+
"command-not-found",
|
|
42
|
+
"nonzero-exit",
|
|
43
|
+
"parse-failed",
|
|
44
|
+
"empty-output",
|
|
45
|
+
"unsupported-mode",
|
|
46
|
+
"unavailable-provider",
|
|
47
|
+
]);
|
|
48
|
+
exports.qcParserResultSchema = zod_1.z.enum(["success", "partial", "failed"]);
|
|
49
|
+
exports.qcProviderAttemptStatusSchema = zod_1.z.enum([
|
|
50
|
+
"success",
|
|
51
|
+
"failure",
|
|
52
|
+
"fallback",
|
|
53
|
+
"skipped",
|
|
54
|
+
]);
|
|
55
|
+
exports.qcProviderAttemptSchema = zod_1.z.object({
|
|
56
|
+
provider: zod_1.z.string(),
|
|
57
|
+
status: exports.qcProviderAttemptStatusSchema,
|
|
58
|
+
failureReason: exports.qcFailureReasonSchema.optional(),
|
|
59
|
+
fallbackSource: zod_1.z.string().optional(),
|
|
60
|
+
rawOutputAvailable: zod_1.z.boolean(),
|
|
61
|
+
rawOutputRetained: zod_1.z.boolean(),
|
|
62
|
+
rawOutputArtifactPath: zod_1.z.string().optional(),
|
|
63
|
+
parserResult: exports.qcParserResultSchema.optional(),
|
|
64
|
+
exitCode: zod_1.z.number().optional(),
|
|
65
|
+
stdoutLength: zod_1.z.number().int().min(0),
|
|
66
|
+
stderrLength: zod_1.z.number().int().min(0),
|
|
67
|
+
});
|
|
36
68
|
exports.qcCodeRangeSchema = zod_1.z.object({
|
|
37
69
|
startLine: zod_1.z.number().int().positive(),
|
|
38
70
|
startColumn: zod_1.z.number().int().positive().optional(),
|
|
@@ -86,6 +118,8 @@ exports.qcResultSchema = zod_1.z.object({
|
|
|
86
118
|
rawArtifactPaths: zod_1.z.array(zod_1.z.string()),
|
|
87
119
|
parserVersion: zod_1.z.string(),
|
|
88
120
|
policyDecision: exports.qcPolicyDecisionSchema,
|
|
121
|
+
providerAttempt: exports.qcProviderAttemptSchema.optional(),
|
|
122
|
+
allProvidersFailed: zod_1.z.boolean().optional(),
|
|
89
123
|
});
|
|
90
124
|
/**
|
|
91
125
|
* Validate an unknown value as a normalized QC result.
|
|
@@ -108,3 +142,46 @@ function validateQcFinding(value) {
|
|
|
108
142
|
}
|
|
109
143
|
return { success: false, errors: parsed.error.issues.map((issue) => issue.message) };
|
|
110
144
|
}
|
|
145
|
+
exports.qcRepairPacketStatusSchema = zod_1.z.enum([
|
|
146
|
+
"pending",
|
|
147
|
+
"dispatched",
|
|
148
|
+
"completed",
|
|
149
|
+
"failed",
|
|
150
|
+
"escalated",
|
|
151
|
+
]);
|
|
152
|
+
exports.qcRepairPacketSchema = zod_1.z.object({
|
|
153
|
+
packetId: zod_1.z.string(),
|
|
154
|
+
round: zod_1.z.number().int().min(0),
|
|
155
|
+
clusterId: zod_1.z.string(),
|
|
156
|
+
sourceQcRunIds: zod_1.z.array(zod_1.z.string()),
|
|
157
|
+
findingIds: zod_1.z.array(zod_1.z.string()),
|
|
158
|
+
severityFloor: exports.qcSeveritySchema,
|
|
159
|
+
rootCauseHint: zod_1.z.string(),
|
|
160
|
+
allowedScope: zod_1.z.array(zod_1.z.string()),
|
|
161
|
+
prohibitedScope: zod_1.z.array(zod_1.z.string()),
|
|
162
|
+
validationCommands: zod_1.z.array(zod_1.z.string()),
|
|
163
|
+
routingTarget: exports.qcRoutingDecisionSchema,
|
|
164
|
+
parallelGroup: zod_1.z.string().nullable(),
|
|
165
|
+
conflicts: zod_1.z.array(zod_1.z.string()),
|
|
166
|
+
medic: zod_1.z.boolean(),
|
|
167
|
+
status: exports.qcRepairPacketStatusSchema,
|
|
168
|
+
createdAt: zod_1.z.string().datetime(),
|
|
169
|
+
});
|
|
170
|
+
exports.qcRepairPacketManifestSchema = zod_1.z.object({
|
|
171
|
+
schemaVersion: zod_1.z.string(),
|
|
172
|
+
clusterId: zod_1.z.string(),
|
|
173
|
+
round: zod_1.z.number().int().min(0),
|
|
174
|
+
compiledAt: zod_1.z.string().datetime(),
|
|
175
|
+
sourceQcRunIds: zod_1.z.array(zod_1.z.string()),
|
|
176
|
+
packets: zod_1.z.array(exports.qcRepairPacketSchema),
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* Validate an unknown value as a compiled repair packet manifest.
|
|
180
|
+
*/
|
|
181
|
+
function validateRepairPacketManifest(value) {
|
|
182
|
+
const parsed = exports.qcRepairPacketManifestSchema.safeParse(value);
|
|
183
|
+
if (parsed.success) {
|
|
184
|
+
return { success: true, manifest: parsed.data };
|
|
185
|
+
}
|
|
186
|
+
return { success: false, errors: parsed.error.issues.map((issue) => issue.message) };
|
|
187
|
+
}
|