@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
|
@@ -253,6 +253,11 @@ function normalizeQcScoreSummary(raw) {
|
|
|
253
253
|
category_breakdown: q.category_breakdown ?? {},
|
|
254
254
|
recurring_child_signals: Array.isArray(q.recurring_child_signals) ? q.recurring_child_signals : [],
|
|
255
255
|
recurring_provider_signals: Array.isArray(q.recurring_provider_signals) ? q.recurring_provider_signals : [],
|
|
256
|
+
repair_loop: q.repair_loop ?? null,
|
|
257
|
+
noisy_providers: Array.isArray(q.noisy_providers) ? q.noisy_providers : [],
|
|
258
|
+
has_repair_failures: q.has_repair_failures ?? false,
|
|
259
|
+
unresolved_high_severity: q.unresolved_high_severity ?? 0,
|
|
260
|
+
max_round_exhausted: q.max_round_exhausted ?? false,
|
|
256
261
|
};
|
|
257
262
|
}
|
|
258
263
|
/**
|
|
@@ -25,6 +25,7 @@ exports.scoreRun = scoreRun;
|
|
|
25
25
|
const node_fs_1 = require("node:fs");
|
|
26
26
|
const node_path_1 = require("node:path");
|
|
27
27
|
const artifacts_js_1 = require("../qc/artifacts.js");
|
|
28
|
+
const store_js_1 = require("../cluster-state/store.js");
|
|
28
29
|
const gates_js_1 = require("./gates.js");
|
|
29
30
|
// ──────────────────────────────────────────────
|
|
30
31
|
// Diagnosis hints table
|
|
@@ -196,6 +197,7 @@ function loadRunArtifacts(repoRoot, runId) {
|
|
|
196
197
|
workerResultContracts = extractWorkerResultContracts(resultPackets);
|
|
197
198
|
}
|
|
198
199
|
const qcResults = loadQcArtifacts(clusterId, repoRoot);
|
|
200
|
+
const clusterState = clusterId ? (0, store_js_1.readClusterStateSync)(clusterId, repoRoot) : null;
|
|
199
201
|
return {
|
|
200
202
|
runId,
|
|
201
203
|
runDir,
|
|
@@ -206,6 +208,7 @@ function loadRunArtifacts(repoRoot, runId) {
|
|
|
206
208
|
workerResultContracts,
|
|
207
209
|
telemetryEvents,
|
|
208
210
|
qcResults,
|
|
211
|
+
clusterState,
|
|
209
212
|
};
|
|
210
213
|
}
|
|
211
214
|
// ──────────────────────────────────────────────
|
|
@@ -247,6 +250,159 @@ function computeQcPenalty(weightedOpenScore) {
|
|
|
247
250
|
// Soft penalty that grows with weighted severity but does not dominate the score.
|
|
248
251
|
return weightedOpenScore === 0 ? 0 : weightedOpenScore / (weightedOpenScore + 20);
|
|
249
252
|
}
|
|
253
|
+
function zeroProviderAttemptSummary() {
|
|
254
|
+
return { total: 0, success: 0, failure: 0, fallback: 0, skipped: 0, all_providers_failed: false };
|
|
255
|
+
}
|
|
256
|
+
function zeroRepairLoopSummary() {
|
|
257
|
+
return {
|
|
258
|
+
status: "not-configured",
|
|
259
|
+
rounds_completed: 0,
|
|
260
|
+
max_rounds: 0,
|
|
261
|
+
packets_compiled: 0,
|
|
262
|
+
packets_completed: 0,
|
|
263
|
+
packets_failed: 0,
|
|
264
|
+
rerun_outcome: null,
|
|
265
|
+
provider_attempts: zeroProviderAttemptSummary(),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function mapTerminalOutcome(outcome) {
|
|
269
|
+
switch (outcome) {
|
|
270
|
+
case "pass":
|
|
271
|
+
return "passed";
|
|
272
|
+
case "no-repairable":
|
|
273
|
+
return "no-repairable";
|
|
274
|
+
case "max-rounds":
|
|
275
|
+
return "max-rounds";
|
|
276
|
+
case "all-providers-failed":
|
|
277
|
+
return "all-providers-failed";
|
|
278
|
+
case "operator-review":
|
|
279
|
+
return "operator-review";
|
|
280
|
+
case "medic-referral":
|
|
281
|
+
return "medic-referral";
|
|
282
|
+
case "qc-disabled":
|
|
283
|
+
return "not-configured";
|
|
284
|
+
default:
|
|
285
|
+
return "unknown";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function providerAttemptStatusFromResult(result) {
|
|
289
|
+
if (result.providerAttempt)
|
|
290
|
+
return result.providerAttempt.status;
|
|
291
|
+
if (result.allProvidersFailed || result.status === "failed")
|
|
292
|
+
return "failure";
|
|
293
|
+
if (result.status === "skipped")
|
|
294
|
+
return "skipped";
|
|
295
|
+
if (result.findings.length > 0 || result.status === "findings" || result.status === "blocked")
|
|
296
|
+
return "success";
|
|
297
|
+
if (result.status === "passed")
|
|
298
|
+
return "success";
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
function computeProviderAttemptSummary(qcResults, telemetryEvents) {
|
|
302
|
+
const summary = zeroProviderAttemptSummary();
|
|
303
|
+
for (const result of qcResults) {
|
|
304
|
+
const status = providerAttemptStatusFromResult(result);
|
|
305
|
+
if (status) {
|
|
306
|
+
summary.total += 1;
|
|
307
|
+
if (status === "success")
|
|
308
|
+
summary.success += 1;
|
|
309
|
+
else if (status === "failure")
|
|
310
|
+
summary.failure += 1;
|
|
311
|
+
else if (status === "fallback")
|
|
312
|
+
summary.fallback += 1;
|
|
313
|
+
else if (status === "skipped")
|
|
314
|
+
summary.skipped += 1;
|
|
315
|
+
}
|
|
316
|
+
if (result.allProvidersFailed)
|
|
317
|
+
summary.all_providers_failed = true;
|
|
318
|
+
}
|
|
319
|
+
// Telemetry fallback events augment the fallback count when providerAttempt was not written.
|
|
320
|
+
for (const ev of telemetryEvents) {
|
|
321
|
+
const rec = asRecord(ev);
|
|
322
|
+
if (rec?.["event"] === "provider-fallback-attempted")
|
|
323
|
+
summary.fallback += 1;
|
|
324
|
+
}
|
|
325
|
+
return summary;
|
|
326
|
+
}
|
|
327
|
+
function computeQcRepairLoopSummary(qcResults, clusterState, telemetryEvents, currentState) {
|
|
328
|
+
if (qcResults.length === 0)
|
|
329
|
+
return zeroRepairLoopSummary();
|
|
330
|
+
const currentStateRec = asRecord(currentState);
|
|
331
|
+
const loopState = currentStateRec?.["qc_repair_loop"];
|
|
332
|
+
const terminalOutcome = clusterState?.qc_repair_outcome ??
|
|
333
|
+
(loopState?.terminal_outcome ? String(loopState.terminal_outcome) : null);
|
|
334
|
+
const terminalEvents = telemetryEvents
|
|
335
|
+
.map(asRecord)
|
|
336
|
+
.filter((e) => e !== undefined && e["event"] === "qc-repair-loop-terminal");
|
|
337
|
+
const lastTerminal = terminalEvents[terminalEvents.length - 1];
|
|
338
|
+
const manifestEvents = telemetryEvents
|
|
339
|
+
.map(asRecord)
|
|
340
|
+
.filter((e) => e !== undefined && e["event"] === "qc-repair-manifest-compiled");
|
|
341
|
+
const failureEvents = telemetryEvents
|
|
342
|
+
.map(asRecord)
|
|
343
|
+
.filter((e) => e !== undefined && e["event"] === "qc-repair-worker-failures");
|
|
344
|
+
const failedPacketIds = new Set();
|
|
345
|
+
for (const ev of failureEvents) {
|
|
346
|
+
const ids = ev["failed_packet_ids"];
|
|
347
|
+
if (Array.isArray(ids)) {
|
|
348
|
+
for (const id of ids) {
|
|
349
|
+
if (typeof id === "string")
|
|
350
|
+
failedPacketIds.add(id);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const rerunEvents = telemetryEvents
|
|
355
|
+
.map(asRecord)
|
|
356
|
+
.filter((e) => e !== undefined && e["event"] === "qc-repair-rerun-complete");
|
|
357
|
+
const lastRerun = rerunEvents[rerunEvents.length - 1];
|
|
358
|
+
let status;
|
|
359
|
+
if (terminalOutcome) {
|
|
360
|
+
status = mapTerminalOutcome(terminalOutcome);
|
|
361
|
+
}
|
|
362
|
+
else if (lastTerminal) {
|
|
363
|
+
status = mapTerminalOutcome(asString(lastTerminal["outcome"]));
|
|
364
|
+
}
|
|
365
|
+
else if (loopState) {
|
|
366
|
+
status = "in-progress";
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
status = "not-run";
|
|
370
|
+
}
|
|
371
|
+
const roundsCompleted = asNumber(lastTerminal?.["rounds_completed"]) ??
|
|
372
|
+
(loopState?.current_round ?? 0);
|
|
373
|
+
const maxRounds = asNumber(lastTerminal?.["max_rounds"]) ??
|
|
374
|
+
(loopState?.max_rounds ?? 0);
|
|
375
|
+
const packetsCompiled = manifestEvents.reduce((sum, e) => {
|
|
376
|
+
const n = asNumber(e["packet_count"]);
|
|
377
|
+
return sum + (n ?? 0);
|
|
378
|
+
}, 0);
|
|
379
|
+
const packetsFailed = failedPacketIds.size;
|
|
380
|
+
const packetsCompleted = status === "not-run" || status === "not-configured"
|
|
381
|
+
? 0
|
|
382
|
+
: Math.max(0, packetsCompiled - packetsFailed);
|
|
383
|
+
let rerunOutcome = null;
|
|
384
|
+
if (lastRerun) {
|
|
385
|
+
const action = asString(lastRerun["action"]);
|
|
386
|
+
if (action === "pass")
|
|
387
|
+
rerunOutcome = "pass";
|
|
388
|
+
else if (action === "findings")
|
|
389
|
+
rerunOutcome = "findings";
|
|
390
|
+
else if (action === "failure" || action === "error")
|
|
391
|
+
rerunOutcome = "failed";
|
|
392
|
+
else if (action === "skipped")
|
|
393
|
+
rerunOutcome = "skipped";
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
status,
|
|
397
|
+
rounds_completed: roundsCompleted,
|
|
398
|
+
max_rounds: maxRounds,
|
|
399
|
+
packets_compiled: packetsCompiled,
|
|
400
|
+
packets_completed: packetsCompleted,
|
|
401
|
+
packets_failed: packetsFailed,
|
|
402
|
+
rerun_outcome: rerunOutcome,
|
|
403
|
+
provider_attempts: computeProviderAttemptSummary(qcResults, telemetryEvents),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
250
406
|
/**
|
|
251
407
|
* Computes a QcScoreSummary from the QC results loaded for this run.
|
|
252
408
|
* Returns null when no QC results are present.
|
|
@@ -263,7 +419,7 @@ function computeQcPenalty(weightedOpenScore) {
|
|
|
263
419
|
* a critical/high-confidence finding hurts SOL scoring more than a low-severity
|
|
264
420
|
* or uncertain finding.
|
|
265
421
|
*/
|
|
266
|
-
function computeQcSummary(qcResults) {
|
|
422
|
+
function computeQcSummary(qcResults, clusterState = null, telemetryEvents = [], currentState = null) {
|
|
267
423
|
if (qcResults.length === 0)
|
|
268
424
|
return null;
|
|
269
425
|
let total = 0;
|
|
@@ -362,6 +518,11 @@ function computeQcSummary(qcResults) {
|
|
|
362
518
|
.map(([provider, value]) => ({ provider, ...value }))
|
|
363
519
|
.filter((s) => s.weighted_score > 0)
|
|
364
520
|
.sort((a, b) => b.weighted_score - a.weighted_score || a.provider.localeCompare(b.provider));
|
|
521
|
+
const repairLoop = computeQcRepairLoopSummary(qcResults, clusterState, telemetryEvents, currentState);
|
|
522
|
+
const noisyProviders = Array.from(providerBreakdown.entries())
|
|
523
|
+
.filter(([, counts]) => counts.total >= 2 && counts.unvalidated / counts.total >= 0.5)
|
|
524
|
+
.map(([provider]) => provider)
|
|
525
|
+
.sort();
|
|
365
526
|
return {
|
|
366
527
|
total_findings: total,
|
|
367
528
|
blocking_findings: blocking,
|
|
@@ -379,6 +540,11 @@ function computeQcSummary(qcResults) {
|
|
|
379
540
|
category_breakdown: Object.fromEntries(categoryBreakdown),
|
|
380
541
|
recurring_child_signals: recurringChildSignals,
|
|
381
542
|
recurring_provider_signals: recurringProviderSignals,
|
|
543
|
+
repair_loop: repairLoop,
|
|
544
|
+
noisy_providers: noisyProviders,
|
|
545
|
+
has_repair_failures: repairLoop.packets_failed > 0 || repairLoop.status === "medic-referral",
|
|
546
|
+
unresolved_high_severity: openBySeverity.critical + openBySeverity.high,
|
|
547
|
+
max_round_exhausted: repairLoop.status === "max-rounds",
|
|
382
548
|
};
|
|
383
549
|
}
|
|
384
550
|
function asRecord(value) {
|
|
@@ -386,6 +552,12 @@ function asRecord(value) {
|
|
|
386
552
|
? value
|
|
387
553
|
: undefined;
|
|
388
554
|
}
|
|
555
|
+
function asString(value) {
|
|
556
|
+
return typeof value === "string" ? value : null;
|
|
557
|
+
}
|
|
558
|
+
function asNumber(value) {
|
|
559
|
+
return typeof value === "number" ? value : null;
|
|
560
|
+
}
|
|
389
561
|
function asStringArray(value) {
|
|
390
562
|
if (!Array.isArray(value))
|
|
391
563
|
return [];
|
|
@@ -485,7 +657,7 @@ function scoreRun(repoRoot, runId) {
|
|
|
485
657
|
const score = computeScore(gateResults);
|
|
486
658
|
const diagnosisHints = buildDiagnosisHints(failedGates);
|
|
487
659
|
const routerOutcomes = summarizeRouterOutcomes(artifacts);
|
|
488
|
-
const qcSummary = computeQcSummary(artifacts.qcResults);
|
|
660
|
+
const qcSummary = computeQcSummary(artifacts.qcResults, artifacts.clusterState, artifacts.telemetryEvents, artifacts.currentState);
|
|
489
661
|
const clusterId = artifacts.currentState &&
|
|
490
662
|
typeof artifacts.currentState === "object" &&
|
|
491
663
|
!Array.isArray(artifacts.currentState)
|
|
@@ -293,9 +293,11 @@ function buildRouterEvidence(artifacts) {
|
|
|
293
293
|
// QC evidence
|
|
294
294
|
// ──────────────────────────────────────────────
|
|
295
295
|
function buildQcEvidence(artifacts) {
|
|
296
|
+
const disabledOutcome = artifacts.clusterState?.qc_repair_outcome;
|
|
297
|
+
const isDisabled = disabledOutcome === "qc-disabled";
|
|
296
298
|
if (artifacts.qcResults.length === 0) {
|
|
297
|
-
|
|
298
|
-
availability: "future",
|
|
299
|
+
const base = {
|
|
300
|
+
availability: isDisabled ? "unavailable" : "future",
|
|
299
301
|
qc_run_count: 0,
|
|
300
302
|
total_findings: 0,
|
|
301
303
|
blocking_findings: 0,
|
|
@@ -307,9 +309,32 @@ function buildQcEvidence(artifacts) {
|
|
|
307
309
|
qc_penalty: 0,
|
|
308
310
|
blocks_delivery: false,
|
|
309
311
|
open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
|
312
|
+
provider_breakdown: {},
|
|
313
|
+
repair_loop: {
|
|
314
|
+
status: isDisabled ? "not-configured" : "not-run",
|
|
315
|
+
rounds_completed: 0,
|
|
316
|
+
max_rounds: 0,
|
|
317
|
+
packets_compiled: 0,
|
|
318
|
+
packets_completed: 0,
|
|
319
|
+
packets_failed: 0,
|
|
320
|
+
rerun_outcome: null,
|
|
321
|
+
provider_attempts: {
|
|
322
|
+
total: 0,
|
|
323
|
+
success: 0,
|
|
324
|
+
failure: 0,
|
|
325
|
+
fallback: 0,
|
|
326
|
+
skipped: 0,
|
|
327
|
+
all_providers_failed: false,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
noisy_providers: [],
|
|
331
|
+
has_repair_failures: false,
|
|
332
|
+
unresolved_high_severity: 0,
|
|
333
|
+
max_round_exhausted: false,
|
|
310
334
|
};
|
|
335
|
+
return base;
|
|
311
336
|
}
|
|
312
|
-
const summary = (0, score_js_1.computeQcSummary)(artifacts.qcResults);
|
|
337
|
+
const summary = (0, score_js_1.computeQcSummary)(artifacts.qcResults, artifacts.clusterState, artifacts.telemetryEvents, artifacts.currentState);
|
|
313
338
|
if (!summary) {
|
|
314
339
|
// computeQcSummary only returns null when qcResults is empty; handled above.
|
|
315
340
|
// ponytail: defensive branch to satisfy type narrowing
|
|
@@ -326,6 +351,12 @@ function buildQcEvidence(artifacts) {
|
|
|
326
351
|
qc_penalty: 0,
|
|
327
352
|
blocks_delivery: false,
|
|
328
353
|
open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
|
354
|
+
provider_breakdown: {},
|
|
355
|
+
repair_loop: null,
|
|
356
|
+
noisy_providers: [],
|
|
357
|
+
has_repair_failures: false,
|
|
358
|
+
unresolved_high_severity: 0,
|
|
359
|
+
max_round_exhausted: false,
|
|
329
360
|
};
|
|
330
361
|
}
|
|
331
362
|
return {
|
|
@@ -341,6 +372,12 @@ function buildQcEvidence(artifacts) {
|
|
|
341
372
|
qc_penalty: summary.qc_penalty,
|
|
342
373
|
blocks_delivery: summary.blocks_delivery,
|
|
343
374
|
open_by_severity: { ...summary.open_by_severity },
|
|
375
|
+
provider_breakdown: { ...summary.provider_breakdown },
|
|
376
|
+
repair_loop: summary.repair_loop,
|
|
377
|
+
noisy_providers: [...summary.noisy_providers],
|
|
378
|
+
has_repair_failures: summary.has_repair_failures,
|
|
379
|
+
unresolved_high_severity: summary.unresolved_high_severity,
|
|
380
|
+
max_round_exhausted: summary.max_round_exhausted,
|
|
344
381
|
};
|
|
345
382
|
}
|
|
346
383
|
// ──────────────────────────────────────────────
|
|
@@ -19,6 +19,8 @@ exports.generateRecommendations = generateRecommendations;
|
|
|
19
19
|
exports.recommendationToProposal = recommendationToProposal;
|
|
20
20
|
exports.recommendationsToProposals = recommendationsToProposals;
|
|
21
21
|
exports.formatRecommendationsCli = formatRecommendationsCli;
|
|
22
|
+
exports.generateQcRecommendations = generateQcRecommendations;
|
|
23
|
+
exports.formatQcRecommendations = formatQcRecommendations;
|
|
22
24
|
const sol_report_js_1 = require("./sol-report.js");
|
|
23
25
|
// ──────────────────────────────────────────────
|
|
24
26
|
// Constants and helpers
|
|
@@ -298,3 +300,83 @@ function formatRecommendationsCli(report) {
|
|
|
298
300
|
}
|
|
299
301
|
return lines.join("\n");
|
|
300
302
|
}
|
|
303
|
+
function makeQcRecommendation(id, category, actionType, affected, proposedAction, rationale, confidence, evidenceRunId) {
|
|
304
|
+
return {
|
|
305
|
+
id,
|
|
306
|
+
category,
|
|
307
|
+
action_type: actionType,
|
|
308
|
+
affected,
|
|
309
|
+
proposed_action: proposedAction,
|
|
310
|
+
confidence: clamp01(confidence),
|
|
311
|
+
evidence: {
|
|
312
|
+
group_key: `run_id=${evidenceRunId}`,
|
|
313
|
+
grouped_by: ["run_id"],
|
|
314
|
+
count: 1,
|
|
315
|
+
mean_composite: null,
|
|
316
|
+
min_composite: null,
|
|
317
|
+
max_composite: null,
|
|
318
|
+
mean_foreman_composite: null,
|
|
319
|
+
mean_worker_composite: null,
|
|
320
|
+
run_ids: [evidenceRunId],
|
|
321
|
+
},
|
|
322
|
+
rationale,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Generate QC repair-loop follow-up recommendations from a single run's
|
|
327
|
+
* SOL evidence. These are advisory signals for noisy providers, repeated
|
|
328
|
+
* repair failures, unresolved high-severity findings, and max-round exhaustion.
|
|
329
|
+
*/
|
|
330
|
+
function generateQcRecommendations(evidence) {
|
|
331
|
+
const recommendations = [];
|
|
332
|
+
const qc = evidence.qc;
|
|
333
|
+
const runId = evidence.run_id;
|
|
334
|
+
if (qc.availability !== "available") {
|
|
335
|
+
return {
|
|
336
|
+
generated_at: new Date().toISOString(),
|
|
337
|
+
evidence_run_id: runId,
|
|
338
|
+
recommendations,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
for (const provider of qc.noisy_providers) {
|
|
342
|
+
const counts = qc.provider_breakdown[provider] ?? { total: 0, blocking: 0, unvalidated: 0 };
|
|
343
|
+
const confidence = counts.total > 0 ? counts.unvalidated / counts.total : 0;
|
|
344
|
+
recommendations.push(makeQcRecommendation(`qc-noisy-provider:${provider}`, "provider_policy", "analyze", { provider }, `Review QC provider '${provider}' for noisy/unvalidated findings; consider raising attribution confidence thresholds or disabling the provider for high-risk routes.`, `${counts.unvalidated}/${counts.total} findings from '${provider}' are unvalidated noise.`, confidence, runId));
|
|
345
|
+
}
|
|
346
|
+
if (qc.has_repair_failures) {
|
|
347
|
+
const failed = qc.repair_loop?.packets_failed ?? 0;
|
|
348
|
+
recommendations.push(makeQcRecommendation(`qc-repair-failure:${runId}`, "qc_follow_up", "implement", {}, "Repeated repair worker failures detected — inspect repair packet scope, validation commands, and Medic referral path.", `${failed} repair packet(s) failed; consider tightening acceptance criteria or escalating to Medic.`, 0.9, runId));
|
|
349
|
+
}
|
|
350
|
+
if (qc.unresolved_high_severity > 0) {
|
|
351
|
+
recommendations.push(makeQcRecommendation(`qc-unresolved-high-severity:${runId}`, "qc_follow_up", "analyze", {}, `${qc.unresolved_high_severity} unresolved critical/high QC findings remain — triage before delivery.`, `${qc.unresolved_high_severity} critical/high findings are still open after repair loop.`, Math.min(1, qc.unresolved_high_severity / 5), runId));
|
|
352
|
+
}
|
|
353
|
+
if (qc.max_round_exhausted) {
|
|
354
|
+
recommendations.push(makeQcRecommendation(`qc-max-rounds:${runId}`, "qc_follow_up", "analyze", {}, "QC repair loop exhausted max rounds — review provider noise, repair packet scope, and escalation policy.", `Repair loop reached round ${qc.repair_loop?.rounds_completed ?? 0}/${qc.repair_loop?.max_rounds ?? 0} without passing.`, 0.85, runId));
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
generated_at: new Date().toISOString(),
|
|
358
|
+
evidence_run_id: runId,
|
|
359
|
+
recommendations,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function formatQcRecommendations(report) {
|
|
363
|
+
const lines = [];
|
|
364
|
+
lines.push("SOL QC Follow-Up Recommendations");
|
|
365
|
+
lines.push(`Generated: ${report.generated_at}`);
|
|
366
|
+
lines.push(`Run: ${report.evidence_run_id}`);
|
|
367
|
+
lines.push("");
|
|
368
|
+
if (report.recommendations.length === 0) {
|
|
369
|
+
lines.push("No QC follow-up signals detected.");
|
|
370
|
+
return lines.join("\n") + "\n";
|
|
371
|
+
}
|
|
372
|
+
for (const r of report.recommendations) {
|
|
373
|
+
lines.push(`[${r.action_type}] ${r.id}`);
|
|
374
|
+
lines.push(` Category: ${r.category}`);
|
|
375
|
+
lines.push(` Affected: ${fmtAffected(r.affected)}`);
|
|
376
|
+
lines.push(` Confidence: ${(r.confidence * 100).toFixed(1)}%`);
|
|
377
|
+
lines.push(` Action: ${r.proposed_action}`);
|
|
378
|
+
lines.push(` Rationale: ${r.rationale}`);
|
|
379
|
+
lines.push("");
|
|
380
|
+
}
|
|
381
|
+
return lines.join("\n");
|
|
382
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.generateReport = generateReport;
|
|
13
13
|
exports.formatReportCli = formatReportCli;
|
|
14
|
+
exports.formatQcEvidence = formatQcEvidence;
|
|
14
15
|
// ──────────────────────────────────────────────
|
|
15
16
|
// Helpers
|
|
16
17
|
// ──────────────────────────────────────────────
|
|
@@ -151,3 +152,46 @@ function formatReportCli(report) {
|
|
|
151
152
|
function padRight(s, n) {
|
|
152
153
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
153
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Render a human-readable summary of QC evidence and repair-loop outcomes.
|
|
157
|
+
*/
|
|
158
|
+
function formatQcEvidence(qc) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
lines.push("QC Evidence Summary");
|
|
161
|
+
lines.push(` availability: ${qc.availability}`);
|
|
162
|
+
lines.push(` qc_run_count: ${qc.qc_run_count}`);
|
|
163
|
+
lines.push(` total_findings: ${qc.total_findings}`);
|
|
164
|
+
lines.push(` repaired: ${qc.repaired_findings}, open: ${qc.total_findings - qc.repaired_findings - qc.autofixed_findings - qc.waived_findings}`);
|
|
165
|
+
lines.push(` unresolved high/critical: ${qc.unresolved_high_severity}`);
|
|
166
|
+
lines.push(` blocks_delivery: ${qc.blocks_delivery}`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
const providers = Object.entries(qc.provider_breakdown);
|
|
169
|
+
if (providers.length > 0) {
|
|
170
|
+
lines.push("Provider breakdown:");
|
|
171
|
+
for (const [provider, counts] of providers) {
|
|
172
|
+
lines.push(` ${provider}: total=${counts.total}, blocking=${counts.blocking}, unvalidated=${counts.unvalidated}`);
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
176
|
+
if (qc.repair_loop) {
|
|
177
|
+
const loop = qc.repair_loop;
|
|
178
|
+
lines.push("Repair loop:");
|
|
179
|
+
lines.push(` status: ${loop.status}`);
|
|
180
|
+
lines.push(` rounds: ${loop.rounds_completed}/${loop.max_rounds}`);
|
|
181
|
+
lines.push(` packets: ${loop.packets_compiled} compiled, ${loop.packets_completed} completed, ${loop.packets_failed} failed`);
|
|
182
|
+
lines.push(` rerun_outcome: ${loop.rerun_outcome ?? "n/a"}`);
|
|
183
|
+
lines.push(` provider_attempts: total=${loop.provider_attempts.total}, success=${loop.provider_attempts.success}, failure=${loop.provider_attempts.failure}, fallback=${loop.provider_attempts.fallback}, skipped=${loop.provider_attempts.skipped}`);
|
|
184
|
+
lines.push(` all_providers_failed: ${loop.provider_attempts.all_providers_failed}`);
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
if (qc.noisy_providers.length > 0) {
|
|
188
|
+
lines.push(`Noisy providers: ${qc.noisy_providers.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
if (qc.has_repair_failures) {
|
|
191
|
+
lines.push("Repeated repair failures detected.");
|
|
192
|
+
}
|
|
193
|
+
if (qc.max_round_exhausted) {
|
|
194
|
+
lines.push("Max repair rounds exhausted.");
|
|
195
|
+
}
|
|
196
|
+
return lines.join("\n").trim() + "\n";
|
|
197
|
+
}
|
|
@@ -207,6 +207,48 @@ function scoreForemanCompletion(ev) {
|
|
|
207
207
|
detail: `succeeded=${succeeded}/${total}`,
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* qc_repair_loop: Observe QC repair loop outcomes.
|
|
212
|
+
*
|
|
213
|
+
* Scores repair loop terminal states without treating provider findings as
|
|
214
|
+
* ground truth. Best outcome = loop passed or no repairable findings.
|
|
215
|
+
* Worst = all providers failed, operator review required, or Medic referral.
|
|
216
|
+
* Skipped when QC is not configured or no repair loop ran.
|
|
217
|
+
*/
|
|
218
|
+
function scoreForemanQcRepairLoop(ev) {
|
|
219
|
+
if (ev.qc.availability === "future" || ev.qc.availability === "unavailable") {
|
|
220
|
+
return skipped("qc_repair_loop", `QC evidence availability=${ev.qc.availability}`);
|
|
221
|
+
}
|
|
222
|
+
const loop = ev.qc.repair_loop;
|
|
223
|
+
if (!loop) {
|
|
224
|
+
return skipped("qc_repair_loop", "no QC repair loop data available");
|
|
225
|
+
}
|
|
226
|
+
const { status, rounds_completed, max_rounds, packets_compiled, packets_failed, rerun_outcome } = loop;
|
|
227
|
+
switch (status) {
|
|
228
|
+
case "passed":
|
|
229
|
+
case "repaired":
|
|
230
|
+
case "no-repairable":
|
|
231
|
+
return dim("qc_repair_loop", 1.0, "high", {
|
|
232
|
+
detail: `status=${status}, rounds=${rounds_completed}/${max_rounds}, packets=${packets_compiled}, rerun=${rerun_outcome ?? "n/a"}`,
|
|
233
|
+
});
|
|
234
|
+
case "max-rounds":
|
|
235
|
+
return dim("qc_repair_loop", 0.5, "high", {
|
|
236
|
+
detail: `status=${status}, rounds=${rounds_completed}/${max_rounds}, packets=${packets_compiled}`,
|
|
237
|
+
});
|
|
238
|
+
case "all-providers-failed":
|
|
239
|
+
case "operator-review":
|
|
240
|
+
case "medic-referral":
|
|
241
|
+
return dim("qc_repair_loop", 0.0, "high", {
|
|
242
|
+
detail: `status=${status}, failed_packets=${packets_failed}, rerun=${rerun_outcome ?? "n/a"}`,
|
|
243
|
+
});
|
|
244
|
+
case "not-run":
|
|
245
|
+
case "not-configured":
|
|
246
|
+
case "in-progress":
|
|
247
|
+
case "unknown":
|
|
248
|
+
default:
|
|
249
|
+
return skipped("qc_repair_loop", `QC repair loop status=${status}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
210
252
|
/**
|
|
211
253
|
* recovery: State repair detection.
|
|
212
254
|
* Score = 1.0 when no medic/repair artifacts detected; 0.0 when repair required.
|
|
@@ -388,10 +430,11 @@ function computeForemanScore(ev) {
|
|
|
388
430
|
const scopeDim = scoreForemanScope(ev);
|
|
389
431
|
const completionDim = scoreForemanCompletion(ev);
|
|
390
432
|
const recoveryDim = scoreForemanRecovery(ev);
|
|
433
|
+
const qcRepairLoopDim = scoreForemanQcRepairLoop(ev);
|
|
391
434
|
const dims = [
|
|
392
435
|
tokenDim, durationDim, interventionDim, preAnalysisDim,
|
|
393
436
|
dependencyDim, dispatchDim, evidenceValidationDim, scopeDim,
|
|
394
|
-
completionDim, recoveryDim,
|
|
437
|
+
completionDim, recoveryDim, qcRepairLoopDim,
|
|
395
438
|
];
|
|
396
439
|
const { score: composite_score, confidence: composite_confidence } = composite(dims);
|
|
397
440
|
return {
|
|
@@ -407,6 +450,7 @@ function computeForemanScore(ev) {
|
|
|
407
450
|
scope: scopeDim,
|
|
408
451
|
completion: completionDim,
|
|
409
452
|
recovery: recoveryDim,
|
|
453
|
+
qc_repair_loop: qcRepairLoopDim,
|
|
410
454
|
};
|
|
411
455
|
}
|
|
412
456
|
/**
|