@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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.routeProposals = exports.validateDiagnosisReport = exports.loadDiagnosisReport = exports.buildProposals = exports.FIX_ZONE_MAP = exports.ALL_GATES = exports.buildDiagnosisHints = exports.computeScore = exports.loadRunArtifacts = exports.scoreRun = exports.assertPolarisDevContext = exports.isPolarisDevContext = void 0;
3
+ exports.formatRecommendationsCli = exports.recommendationToProposal = exports.recommendationsToProposals = exports.generateRecommendations = exports.formatReportCli = exports.generateReport = exports.getHistoryFilePath = exports.buildSnapshot = exports.loadSnapshots = exports.appendSnapshot = exports.computeSolScoreReport = exports.computeWorkerScore = exports.computeForemanScore = exports.aggregateSolEvidence = exports.routeProposals = exports.validateDiagnosisReport = exports.loadDiagnosisReport = exports.buildProposals = exports.FIX_ZONE_MAP = exports.ALL_GATES = exports.buildDiagnosisHints = exports.computeScore = exports.loadRunArtifacts = exports.scoreRun = exports.assertPolarisDevContext = exports.isPolarisDevContext = void 0;
4
4
  var dev_gate_js_1 = require("./dev-gate.js");
5
5
  Object.defineProperty(exports, "isPolarisDevContext", { enumerable: true, get: function () { return dev_gate_js_1.isPolarisDevContext; } });
6
6
  Object.defineProperty(exports, "assertPolarisDevContext", { enumerable: true, get: function () { return dev_gate_js_1.assertPolarisDevContext; } });
@@ -18,3 +18,22 @@ Object.defineProperty(exports, "loadDiagnosisReport", { enumerable: true, get: f
18
18
  Object.defineProperty(exports, "validateDiagnosisReport", { enumerable: true, get: function () { return proposal_js_1.validateDiagnosisReport; } });
19
19
  var routing_js_1 = require("./routing.js");
20
20
  Object.defineProperty(exports, "routeProposals", { enumerable: true, get: function () { return routing_js_1.routeProposals; } });
21
+ var sol_evidence_loader_js_1 = require("./sol-evidence-loader.js");
22
+ Object.defineProperty(exports, "aggregateSolEvidence", { enumerable: true, get: function () { return sol_evidence_loader_js_1.aggregateSolEvidence; } });
23
+ var sol_scorer_js_1 = require("./sol-scorer.js");
24
+ Object.defineProperty(exports, "computeForemanScore", { enumerable: true, get: function () { return sol_scorer_js_1.computeForemanScore; } });
25
+ Object.defineProperty(exports, "computeWorkerScore", { enumerable: true, get: function () { return sol_scorer_js_1.computeWorkerScore; } });
26
+ Object.defineProperty(exports, "computeSolScoreReport", { enumerable: true, get: function () { return sol_scorer_js_1.computeSolScoreReport; } });
27
+ var sol_history_js_1 = require("./sol-history.js");
28
+ Object.defineProperty(exports, "appendSnapshot", { enumerable: true, get: function () { return sol_history_js_1.appendSnapshot; } });
29
+ Object.defineProperty(exports, "loadSnapshots", { enumerable: true, get: function () { return sol_history_js_1.loadSnapshots; } });
30
+ Object.defineProperty(exports, "buildSnapshot", { enumerable: true, get: function () { return sol_history_js_1.buildSnapshot; } });
31
+ Object.defineProperty(exports, "getHistoryFilePath", { enumerable: true, get: function () { return sol_history_js_1.getHistoryFilePath; } });
32
+ var sol_report_js_1 = require("./sol-report.js");
33
+ Object.defineProperty(exports, "generateReport", { enumerable: true, get: function () { return sol_report_js_1.generateReport; } });
34
+ Object.defineProperty(exports, "formatReportCli", { enumerable: true, get: function () { return sol_report_js_1.formatReportCli; } });
35
+ var sol_recommendations_js_1 = require("./sol-recommendations.js");
36
+ Object.defineProperty(exports, "generateRecommendations", { enumerable: true, get: function () { return sol_recommendations_js_1.generateRecommendations; } });
37
+ Object.defineProperty(exports, "recommendationsToProposals", { enumerable: true, get: function () { return sol_recommendations_js_1.recommendationsToProposals; } });
38
+ Object.defineProperty(exports, "recommendationToProposal", { enumerable: true, get: function () { return sol_recommendations_js_1.recommendationToProposal; } });
39
+ Object.defineProperty(exports, "formatRecommendationsCli", { enumerable: true, get: function () { return sol_recommendations_js_1.formatRecommendationsCli; } });
@@ -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)