@lsctech/polaris 0.5.5 → 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/index.js +20 -1
- package/dist/autoresearch/proposal.js +5 -0
- package/dist/autoresearch/score.js +174 -2
- package/dist/autoresearch/sol-evidence-loader.js +516 -0
- package/dist/autoresearch/sol-history.js +90 -0
- package/dist/autoresearch/sol-recommendations.js +382 -0
- package/dist/autoresearch/sol-report.js +197 -0
- package/dist/autoresearch/sol-scorer.js +524 -0
- package/dist/cli/autoresearch.js +185 -11
- package/dist/cli/index.js +1 -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/dist/types/sol-evidence.js +18 -0
- package/dist/types/sol-score.js +18 -0
- 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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOL evidence schema.
|
|
4
|
+
*
|
|
5
|
+
* Typed inputs for the Self-Optimization Loop (SOL) scoring pipeline.
|
|
6
|
+
* These types represent the normalized read model over existing durable
|
|
7
|
+
* run artifacts: state, telemetry, result packets, QC results, and
|
|
8
|
+
* (future) router decision evidence.
|
|
9
|
+
*
|
|
10
|
+
* Design rules:
|
|
11
|
+
* - All top-level inputs are optional unless the field documents a
|
|
12
|
+
* required identity key (run_id, cluster_id).
|
|
13
|
+
* - Future fields from POL-469 (router evidence) and POL-476 (QC
|
|
14
|
+
* metrics) are marked with an `availability` tag explaining when
|
|
15
|
+
* they will be populated.
|
|
16
|
+
* - Nothing in this file mutates run artifacts or triggers side effects.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOL scoring model types.
|
|
4
|
+
*
|
|
5
|
+
* Defines the typed output of the SOL score reports for Foremen and Workers.
|
|
6
|
+
* Each dimension has a score, confidence, and optional skipped reason so that
|
|
7
|
+
* callers can preserve the full diagnostic signal rather than a single opaque
|
|
8
|
+
* number.
|
|
9
|
+
*
|
|
10
|
+
* Design rules:
|
|
11
|
+
* - score is 0.0–1.0 where 1.0 = optimal and 0.0 = worst observed behavior.
|
|
12
|
+
* - confidence reflects how trustworthy the score is given the evidence.
|
|
13
|
+
* - skipped_reason is set when evidence was absent or insufficient.
|
|
14
|
+
* - All dimensions are optional at the top level; only present when the
|
|
15
|
+
* relevant evidence was observed for this run.
|
|
16
|
+
* - The existing binary gate diagnosis is preserved alongside SOL scores.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|