@lsctech/polaris 0.5.5 → 0.5.6
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/sol-evidence-loader.js +479 -0
- package/dist/autoresearch/sol-history.js +90 -0
- package/dist/autoresearch/sol-recommendations.js +300 -0
- package/dist/autoresearch/sol-report.js +153 -0
- package/dist/autoresearch/sol-scorer.js +480 -0
- package/dist/cli/autoresearch.js +185 -11
- package/dist/cli/index.js +1 -1
- package/dist/types/sol-evidence.js +18 -0
- package/dist/types/sol-score.js +18 -0
- package/package.json +1 -1
|
@@ -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; } });
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOL evidence loader.
|
|
4
|
+
*
|
|
5
|
+
* Aggregates existing durable run artifacts into a SolEvidence record
|
|
6
|
+
* without mutating any artifact. Reads from RunArtifacts (already loaded
|
|
7
|
+
* by loadRunArtifacts) and maps each field to the normalized SOL schema.
|
|
8
|
+
*
|
|
9
|
+
* Design rules:
|
|
10
|
+
* - Never throws on missing data — tolerate absent fields gracefully.
|
|
11
|
+
* - Future router/QC fields (POL-469, POL-476) are marked "future" when
|
|
12
|
+
* the source system is known but artifacts haven't been written yet.
|
|
13
|
+
* - All file I/O is delegated to loadRunArtifacts; this module is pure
|
|
14
|
+
* aggregation over what was already loaded.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.aggregateSolEvidence = aggregateSolEvidence;
|
|
18
|
+
const node_fs_1 = require("node:fs");
|
|
19
|
+
const score_js_1 = require("./score.js");
|
|
20
|
+
// ──────────────────────────────────────────────
|
|
21
|
+
// Internal helpers
|
|
22
|
+
// ──────────────────────────────────────────────
|
|
23
|
+
function asRecord(v) {
|
|
24
|
+
return v && typeof v === "object" && !Array.isArray(v)
|
|
25
|
+
? v
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
function asStringArray(v) {
|
|
29
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
30
|
+
}
|
|
31
|
+
function asNumber(v) {
|
|
32
|
+
return typeof v === "number" ? v : null;
|
|
33
|
+
}
|
|
34
|
+
function asString(v) {
|
|
35
|
+
return typeof v === "string" ? v : null;
|
|
36
|
+
}
|
|
37
|
+
// ──────────────────────────────────────────────
|
|
38
|
+
// Run evidence
|
|
39
|
+
// ──────────────────────────────────────────────
|
|
40
|
+
function buildRunEvidence(artifacts) {
|
|
41
|
+
const state = asRecord(artifacts.currentState);
|
|
42
|
+
const clusterId = asString(state?.["cluster_id"]);
|
|
43
|
+
const branch = asString(state?.["branch"]);
|
|
44
|
+
const status = asString(state?.["status"]);
|
|
45
|
+
const openChildren = asStringArray(state?.["open_children"]);
|
|
46
|
+
const completedChildren = asStringArray(state?.["completed_children"]);
|
|
47
|
+
const dispatchBoundary = asRecord(state?.["dispatch_boundary"]);
|
|
48
|
+
const dispatchEpoch = asNumber(dispatchBoundary?.["dispatch_epoch"]);
|
|
49
|
+
const continueEpoch = asNumber(dispatchBoundary?.["continue_epoch"]);
|
|
50
|
+
return {
|
|
51
|
+
run_id: artifacts.runId,
|
|
52
|
+
cluster_id: clusterId,
|
|
53
|
+
branch,
|
|
54
|
+
status,
|
|
55
|
+
total_children: openChildren.length + completedChildren.length,
|
|
56
|
+
completed_children: completedChildren.length,
|
|
57
|
+
dispatch_epoch: dispatchEpoch,
|
|
58
|
+
continue_epoch: continueEpoch,
|
|
59
|
+
state_observed_at: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// ──────────────────────────────────────────────
|
|
63
|
+
// Child evidence
|
|
64
|
+
// ──────────────────────────────────────────────
|
|
65
|
+
function buildChildGroupingKeys(contract) {
|
|
66
|
+
const resultData = asRecord(contract.result_data);
|
|
67
|
+
return {
|
|
68
|
+
role: contract.role ?? undefined,
|
|
69
|
+
provider: contract.provider ?? undefined,
|
|
70
|
+
route: asString(resultData?.["route"]) ?? undefined,
|
|
71
|
+
task_type: asString(resultData?.["task_type"]) ?? undefined,
|
|
72
|
+
risk: asString(resultData?.["risk"]) ?? undefined,
|
|
73
|
+
model: asString(resultData?.["model"]) ?? undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function buildChildEvidence(contracts) {
|
|
77
|
+
return contracts.map((c) => ({
|
|
78
|
+
child_id: c.child_id,
|
|
79
|
+
run_id: c.run_id,
|
|
80
|
+
cluster_id: c.cluster_id,
|
|
81
|
+
status: c.status,
|
|
82
|
+
validation: c.validation,
|
|
83
|
+
commit: c.commit ?? null,
|
|
84
|
+
next_recommended_action: c.next_recommended_action,
|
|
85
|
+
role: c.role,
|
|
86
|
+
provider: c.provider,
|
|
87
|
+
skill_name: c.skill_name,
|
|
88
|
+
packet_hash: c.packet_hash,
|
|
89
|
+
worker_id: c.worker_id,
|
|
90
|
+
escalation_count: c.escalation_count,
|
|
91
|
+
heartbeat_count: c.heartbeat_count,
|
|
92
|
+
user_intervened: c.user_intervened ?? null,
|
|
93
|
+
foreman_intervened: c.foreman_intervened ?? null,
|
|
94
|
+
changed_files: c.changed_files ?? [],
|
|
95
|
+
dispatch_epoch: asNumber(c.dispatch_epoch) ?? null,
|
|
96
|
+
grouping_keys: buildChildGroupingKeys(c),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
// ──────────────────────────────────────────────
|
|
100
|
+
// Foreman evidence
|
|
101
|
+
// ──────────────────────────────────────────────
|
|
102
|
+
function buildForemanEvidence(artifacts) {
|
|
103
|
+
// Bootstrap token budget
|
|
104
|
+
const sizeEvents = artifacts.telemetryEvents
|
|
105
|
+
.map(asRecord)
|
|
106
|
+
.filter((e) => e !== undefined && e["event"] === "bootstrap-context-size");
|
|
107
|
+
const tokenValues = sizeEvents
|
|
108
|
+
.map((e) => asNumber(e["combined_estimated_tokens"]))
|
|
109
|
+
.filter((v) => v !== null);
|
|
110
|
+
const maxBootstrapTokens = tokenValues.length > 0 ? Math.max(...tokenValues) : null;
|
|
111
|
+
const overTokenBudget = maxBootstrapTokens !== null && maxBootstrapTokens > 150_000;
|
|
112
|
+
// Re-dispatch detection from per-child dispatch counts in ledger telemetry
|
|
113
|
+
const ledgerCounts = new Map();
|
|
114
|
+
for (const ev of artifacts.ledgerEvents) {
|
|
115
|
+
const rec = asRecord(ev);
|
|
116
|
+
if (rec?.["event"] === "child-dispatched" && typeof rec["issue_id"] === "string") {
|
|
117
|
+
const cid = rec["issue_id"];
|
|
118
|
+
ledgerCounts.set(cid, (ledgerCounts.get(cid) ?? 0) + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const telemetryCounts = new Map();
|
|
122
|
+
for (const ev of artifacts.telemetryEvents) {
|
|
123
|
+
const rec = asRecord(ev);
|
|
124
|
+
if (rec?.["event"] === "child-dispatched" && typeof rec["child_id"] === "string") {
|
|
125
|
+
const cid = rec["child_id"];
|
|
126
|
+
telemetryCounts.set(cid, (telemetryCounts.get(cid) ?? 0) + 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const allCounts = new Map();
|
|
130
|
+
for (const [cid, count] of ledgerCounts)
|
|
131
|
+
allCounts.set(cid, Math.max(allCounts.get(cid) ?? 0, count));
|
|
132
|
+
for (const [cid, count] of telemetryCounts)
|
|
133
|
+
allCounts.set(cid, Math.max(allCounts.get(cid) ?? 0, count));
|
|
134
|
+
const redispatchedChildren = Array.from(allCounts.entries())
|
|
135
|
+
.filter(([, count]) => count > 1)
|
|
136
|
+
.map(([cid]) => cid)
|
|
137
|
+
.sort();
|
|
138
|
+
// Foreman corrective commit
|
|
139
|
+
const foremanCorrectiveCommit = artifacts.workerResultContracts.some((c) => c.foreman_intervened === true);
|
|
140
|
+
// Escalation events
|
|
141
|
+
const escalationEvents = artifacts.telemetryEvents.filter((ev) => {
|
|
142
|
+
const rec = asRecord(ev);
|
|
143
|
+
return rec?.["event"] === "escalation-initiated";
|
|
144
|
+
}).length;
|
|
145
|
+
return {
|
|
146
|
+
max_bootstrap_tokens: maxBootstrapTokens,
|
|
147
|
+
over_token_budget: overTokenBudget,
|
|
148
|
+
redispatch_count: redispatchedChildren.length,
|
|
149
|
+
redispatched_children: redispatchedChildren,
|
|
150
|
+
foreman_corrective_commit: foremanCorrectiveCommit,
|
|
151
|
+
escalation_events: escalationEvents,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// ──────────────────────────────────────────────
|
|
155
|
+
// Worker evidence (aggregate)
|
|
156
|
+
// ──────────────────────────────────────────────
|
|
157
|
+
function buildWorkerEvidence(contracts) {
|
|
158
|
+
let totalHeartbeats = 0;
|
|
159
|
+
let totalEscalations = 0;
|
|
160
|
+
let succeeded = 0;
|
|
161
|
+
let failed = 0;
|
|
162
|
+
let blocked = 0;
|
|
163
|
+
let validationFailed = 0;
|
|
164
|
+
let validationPassed = 0;
|
|
165
|
+
let userInterventions = 0;
|
|
166
|
+
let foremanInterventions = 0;
|
|
167
|
+
for (const c of contracts) {
|
|
168
|
+
totalHeartbeats += c.heartbeat_count;
|
|
169
|
+
totalEscalations += c.escalation_count;
|
|
170
|
+
const status = c.status;
|
|
171
|
+
if (status === "done")
|
|
172
|
+
succeeded++;
|
|
173
|
+
else if (status === "failed" || status === "error")
|
|
174
|
+
failed++;
|
|
175
|
+
else if (status === "blocked")
|
|
176
|
+
blocked++;
|
|
177
|
+
const validation = c.validation;
|
|
178
|
+
if (validation === "passed")
|
|
179
|
+
validationPassed++;
|
|
180
|
+
else if (validation === "failed")
|
|
181
|
+
validationFailed++;
|
|
182
|
+
if (c.user_intervened === true)
|
|
183
|
+
userInterventions++;
|
|
184
|
+
if (c.foreman_intervened === true)
|
|
185
|
+
foremanInterventions++;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
total_heartbeats: totalHeartbeats,
|
|
189
|
+
total_escalations: totalEscalations,
|
|
190
|
+
workers_succeeded: succeeded,
|
|
191
|
+
workers_failed: failed,
|
|
192
|
+
workers_blocked: blocked,
|
|
193
|
+
validation_failures: validationFailed,
|
|
194
|
+
validation_passes: validationPassed,
|
|
195
|
+
user_interventions: userInterventions,
|
|
196
|
+
foreman_interventions: foremanInterventions,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// ──────────────────────────────────────────────
|
|
200
|
+
// Router evidence
|
|
201
|
+
// ──────────────────────────────────────────────
|
|
202
|
+
function buildRouterEvidence(artifacts) {
|
|
203
|
+
const telemetry = artifacts.telemetryEvents
|
|
204
|
+
.map(asRecord)
|
|
205
|
+
.filter((e) => e !== undefined);
|
|
206
|
+
const selectedEvents = telemetry.filter((e) => e["event"] === "provider-selected");
|
|
207
|
+
const exhaustedEvents = telemetry.filter((e) => e["event"] === "provider-exhausted");
|
|
208
|
+
const fallbackEvents = telemetry.filter((e) => e["event"] === "provider-fallback-attempted");
|
|
209
|
+
// No router telemetry at all → future (this is normal before POL-469 lands)
|
|
210
|
+
if (selectedEvents.length === 0 && exhaustedEvents.length === 0 && fallbackEvents.length === 0) {
|
|
211
|
+
return {
|
|
212
|
+
availability: "future",
|
|
213
|
+
total_decisions: 0,
|
|
214
|
+
exhausted_decisions: 0,
|
|
215
|
+
fallback_attempts: 0,
|
|
216
|
+
successful_fallbacks: 0,
|
|
217
|
+
decisions: [],
|
|
218
|
+
recurring_failure_reasons: [],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Build per-decision records
|
|
222
|
+
const decisions = selectedEvents.map((ev) => {
|
|
223
|
+
const childId = asString(ev["child_id"]) ?? "";
|
|
224
|
+
const selectedProvider = asString(ev["selected_provider"]);
|
|
225
|
+
const providersTried = asStringArray(ev["providers_tried"]);
|
|
226
|
+
const exhaustedReason = asString(ev["router_exhausted_reason"]);
|
|
227
|
+
const exhausted = selectedProvider === null;
|
|
228
|
+
const fallbackUsed = selectedProvider !== null && providersTried.length > 1;
|
|
229
|
+
const candidates = Array.isArray(ev["router_candidates"]) ? ev["router_candidates"] : [];
|
|
230
|
+
const rejectionReasons = candidates
|
|
231
|
+
.flatMap((c) => asStringArray(asRecord(c)?.["rejection_reasons"]));
|
|
232
|
+
return {
|
|
233
|
+
child_id: childId,
|
|
234
|
+
selected_provider: selectedProvider,
|
|
235
|
+
providers_tried: providersTried,
|
|
236
|
+
fallback_used: fallbackUsed,
|
|
237
|
+
exhausted,
|
|
238
|
+
exhausted_reason: exhaustedReason,
|
|
239
|
+
rejection_reasons: rejectionReasons,
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
// Successful fallbacks
|
|
243
|
+
const childCompletionStatus = new Map();
|
|
244
|
+
for (const ev of telemetry) {
|
|
245
|
+
if (ev["event"] !== "child-complete" || typeof ev["child_id"] !== "string")
|
|
246
|
+
continue;
|
|
247
|
+
childCompletionStatus.set(ev["child_id"], typeof ev["completion_status"] === "string" ? ev["completion_status"] : "done");
|
|
248
|
+
}
|
|
249
|
+
let successfulFallbacks = 0;
|
|
250
|
+
for (const d of decisions) {
|
|
251
|
+
if (!d.fallback_used)
|
|
252
|
+
continue;
|
|
253
|
+
const cs = childCompletionStatus.get(d.child_id);
|
|
254
|
+
if (cs && cs !== "blocked" && cs !== "error")
|
|
255
|
+
successfulFallbacks++;
|
|
256
|
+
}
|
|
257
|
+
// Recurring failure reasons
|
|
258
|
+
const reasonMap = new Map();
|
|
259
|
+
const countReason = (reason, childId) => {
|
|
260
|
+
const entry = reasonMap.get(reason) ?? { count: 0, childIds: new Set() };
|
|
261
|
+
entry.count++;
|
|
262
|
+
if (childId)
|
|
263
|
+
entry.childIds.add(childId);
|
|
264
|
+
reasonMap.set(reason, entry);
|
|
265
|
+
};
|
|
266
|
+
for (const ev of exhaustedEvents) {
|
|
267
|
+
const reason = asString(ev["reason"]) ?? "no-provider-selected";
|
|
268
|
+
countReason(reason, asString(ev["child_id"]) ?? undefined);
|
|
269
|
+
}
|
|
270
|
+
for (const d of decisions) {
|
|
271
|
+
if (!d.exhausted)
|
|
272
|
+
continue;
|
|
273
|
+
for (const r of d.rejection_reasons)
|
|
274
|
+
countReason(r, d.child_id);
|
|
275
|
+
if (d.rejection_reasons.length === 0 && d.exhausted_reason) {
|
|
276
|
+
countReason(d.exhausted_reason, d.child_id);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const recurringFailureReasons = Array.from(reasonMap.entries())
|
|
280
|
+
.map(([reason, v]) => ({ reason, occurrences: v.count, child_ids: Array.from(v.childIds).sort() }))
|
|
281
|
+
.sort((a, b) => b.occurrences - a.occurrences || a.reason.localeCompare(b.reason));
|
|
282
|
+
return {
|
|
283
|
+
availability: "available",
|
|
284
|
+
total_decisions: selectedEvents.length,
|
|
285
|
+
exhausted_decisions: exhaustedEvents.length,
|
|
286
|
+
fallback_attempts: fallbackEvents.length,
|
|
287
|
+
successful_fallbacks: successfulFallbacks,
|
|
288
|
+
decisions,
|
|
289
|
+
recurring_failure_reasons: recurringFailureReasons,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// ──────────────────────────────────────────────
|
|
293
|
+
// QC evidence
|
|
294
|
+
// ──────────────────────────────────────────────
|
|
295
|
+
function buildQcEvidence(artifacts) {
|
|
296
|
+
if (artifacts.qcResults.length === 0) {
|
|
297
|
+
return {
|
|
298
|
+
availability: "future",
|
|
299
|
+
qc_run_count: 0,
|
|
300
|
+
total_findings: 0,
|
|
301
|
+
blocking_findings: 0,
|
|
302
|
+
autofixed_findings: 0,
|
|
303
|
+
repaired_findings: 0,
|
|
304
|
+
waived_findings: 0,
|
|
305
|
+
unvalidated_findings: 0,
|
|
306
|
+
weighted_open_score: 0,
|
|
307
|
+
qc_penalty: 0,
|
|
308
|
+
blocks_delivery: false,
|
|
309
|
+
open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const summary = (0, score_js_1.computeQcSummary)(artifacts.qcResults);
|
|
313
|
+
if (!summary) {
|
|
314
|
+
// computeQcSummary only returns null when qcResults is empty; handled above.
|
|
315
|
+
// ponytail: defensive branch to satisfy type narrowing
|
|
316
|
+
return {
|
|
317
|
+
availability: "unavailable",
|
|
318
|
+
qc_run_count: 0,
|
|
319
|
+
total_findings: 0,
|
|
320
|
+
blocking_findings: 0,
|
|
321
|
+
autofixed_findings: 0,
|
|
322
|
+
repaired_findings: 0,
|
|
323
|
+
waived_findings: 0,
|
|
324
|
+
unvalidated_findings: 0,
|
|
325
|
+
weighted_open_score: 0,
|
|
326
|
+
qc_penalty: 0,
|
|
327
|
+
blocks_delivery: false,
|
|
328
|
+
open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
availability: "available",
|
|
333
|
+
qc_run_count: summary.qc_run_count,
|
|
334
|
+
total_findings: summary.total_findings,
|
|
335
|
+
blocking_findings: summary.blocking_findings,
|
|
336
|
+
autofixed_findings: summary.autofixed_findings,
|
|
337
|
+
repaired_findings: summary.repaired_findings,
|
|
338
|
+
waived_findings: summary.waived_findings,
|
|
339
|
+
unvalidated_findings: summary.unvalidated_findings,
|
|
340
|
+
weighted_open_score: summary.weighted_open_score,
|
|
341
|
+
qc_penalty: summary.qc_penalty,
|
|
342
|
+
blocks_delivery: summary.blocks_delivery,
|
|
343
|
+
open_by_severity: { ...summary.open_by_severity },
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// ──────────────────────────────────────────────
|
|
347
|
+
// Validation evidence
|
|
348
|
+
// ──────────────────────────────────────────────
|
|
349
|
+
function buildValidationEvidence(contracts) {
|
|
350
|
+
return contracts.map((c) => {
|
|
351
|
+
const validation = c.validation;
|
|
352
|
+
const outcome = typeof validation === "string" ? validation : "unknown";
|
|
353
|
+
// result_data may carry validation details from the sealed result packet
|
|
354
|
+
const resultData = asRecord(c.result_data);
|
|
355
|
+
const passedCommands = asStringArray(asRecord(resultData?.["validation"])?.["passed"] ?? resultData?.["passed_commands"]);
|
|
356
|
+
const errorMessage = asString(resultData?.["error_message"]);
|
|
357
|
+
return {
|
|
358
|
+
child_id: c.child_id,
|
|
359
|
+
outcome,
|
|
360
|
+
passed_commands: passedCommands,
|
|
361
|
+
error_message: errorMessage,
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// ──────────────────────────────────────────────
|
|
366
|
+
// Token evidence
|
|
367
|
+
// ──────────────────────────────────────────────
|
|
368
|
+
function buildTokenEvidence(artifacts) {
|
|
369
|
+
const sizeEvents = artifacts.telemetryEvents
|
|
370
|
+
.map(asRecord)
|
|
371
|
+
.filter((e) => e !== undefined && e["event"] === "bootstrap-context-size");
|
|
372
|
+
const tokenValues = sizeEvents
|
|
373
|
+
.map((e) => asNumber(e["combined_estimated_tokens"]))
|
|
374
|
+
.filter((v) => v !== null);
|
|
375
|
+
const maxBootstrapTokens = tokenValues.length > 0 ? Math.max(...tokenValues) : null;
|
|
376
|
+
const heartbeatEvents = artifacts.telemetryEvents
|
|
377
|
+
.map(asRecord)
|
|
378
|
+
.filter((e) => e !== undefined && e["event"] === "worker-heartbeat");
|
|
379
|
+
const tokensByChild = {};
|
|
380
|
+
for (const ev of heartbeatEvents) {
|
|
381
|
+
const childId = asString(ev["child_id"]);
|
|
382
|
+
const tokens = asNumber(ev["tokens_used"]);
|
|
383
|
+
if (childId && tokens !== null) {
|
|
384
|
+
tokensByChild[childId] = (tokensByChild[childId] ?? 0) + tokens;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
max_bootstrap_tokens: maxBootstrapTokens,
|
|
389
|
+
total_worker_heartbeats: heartbeatEvents.length,
|
|
390
|
+
tokens_by_child: tokensByChild,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// ──────────────────────────────────────────────
|
|
394
|
+
// Intervention evidence
|
|
395
|
+
// ──────────────────────────────────────────────
|
|
396
|
+
function buildInterventionEvidence(artifacts) {
|
|
397
|
+
const userIntervened = artifacts.workerResultContracts.some((c) => c.user_intervened === true);
|
|
398
|
+
const foremanIntervened = artifacts.workerResultContracts.some((c) => c.foreman_intervened === true);
|
|
399
|
+
const blockedEvents = artifacts.telemetryEvents.filter((ev) => {
|
|
400
|
+
const rec = asRecord(ev);
|
|
401
|
+
return rec?.["event"] === "worker-blocked";
|
|
402
|
+
});
|
|
403
|
+
const outOfScopeCount = blockedEvents.filter((ev) => {
|
|
404
|
+
const rec = asRecord(ev);
|
|
405
|
+
return rec?.["approval_type"] === "out-of-scope";
|
|
406
|
+
}).length;
|
|
407
|
+
// Medic state-repair detection from cluster directory (mirrors gateStateRepairRequired)
|
|
408
|
+
let stateRepairRequired = false;
|
|
409
|
+
if (artifacts.clusterDir && (0, node_fs_1.existsSync)(artifacts.clusterDir)) {
|
|
410
|
+
const files = (() => {
|
|
411
|
+
try {
|
|
412
|
+
return (0, node_fs_1.readdirSync)(artifacts.clusterDir);
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
})();
|
|
418
|
+
stateRepairRequired = files.some((f) => f.startsWith("CHART-") || f.startsWith("medic-result-") || (f.includes("medic") && f.endsWith(".json")));
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
user_intervened: userIntervened,
|
|
422
|
+
foreman_intervened: foremanIntervened,
|
|
423
|
+
blocked_event_count: blockedEvents.length,
|
|
424
|
+
out_of_scope_count: outOfScopeCount,
|
|
425
|
+
state_repair_required: stateRepairRequired,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// ──────────────────────────────────────────────
|
|
429
|
+
// Grouping keys from run state
|
|
430
|
+
// ──────────────────────────────────────────────
|
|
431
|
+
function buildRunGroupingKeys(artifacts) {
|
|
432
|
+
const state = asRecord(artifacts.currentState);
|
|
433
|
+
// Try to read grouping keys from run state if a consumer sets them.
|
|
434
|
+
// In v1 these are typically absent; workers may set them via result_data.
|
|
435
|
+
return {
|
|
436
|
+
repo: asString(state?.["repo"]) ?? undefined,
|
|
437
|
+
route: asString(state?.["route"]) ?? undefined,
|
|
438
|
+
task_type: asString(state?.["task_type"]) ?? undefined,
|
|
439
|
+
role: asString(state?.["role"]) ?? undefined,
|
|
440
|
+
risk: asString(state?.["risk"]) ?? undefined,
|
|
441
|
+
provider: asString(state?.["provider"]) ?? undefined,
|
|
442
|
+
model: asString(state?.["model"]) ?? undefined,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// ──────────────────────────────────────────────
|
|
446
|
+
// Main entry point
|
|
447
|
+
// ──────────────────────────────────────────────
|
|
448
|
+
/**
|
|
449
|
+
* Aggregate existing run artifacts into a normalized SolEvidence record.
|
|
450
|
+
*
|
|
451
|
+
* Does not mutate any artifact. Tolerate absent future router/QC fields
|
|
452
|
+
* by marking them "future" rather than throwing.
|
|
453
|
+
*
|
|
454
|
+
* @param artifacts — already-loaded RunArtifacts (from loadRunArtifacts)
|
|
455
|
+
* @returns SolEvidence
|
|
456
|
+
*/
|
|
457
|
+
function aggregateSolEvidence(artifacts) {
|
|
458
|
+
const children = buildChildEvidence(artifacts.workerResultContracts);
|
|
459
|
+
return {
|
|
460
|
+
schema_version: "1.0",
|
|
461
|
+
run_id: artifacts.runId,
|
|
462
|
+
cluster_id: artifacts.currentState &&
|
|
463
|
+
typeof artifacts.currentState === "object" &&
|
|
464
|
+
!Array.isArray(artifacts.currentState)
|
|
465
|
+
? (asString(artifacts.currentState["cluster_id"]))
|
|
466
|
+
: null,
|
|
467
|
+
observed_at: new Date().toISOString(),
|
|
468
|
+
grouping_keys: buildRunGroupingKeys(artifacts),
|
|
469
|
+
run: buildRunEvidence(artifacts),
|
|
470
|
+
children,
|
|
471
|
+
foreman: buildForemanEvidence(artifacts),
|
|
472
|
+
worker: buildWorkerEvidence(artifacts.workerResultContracts),
|
|
473
|
+
router: buildRouterEvidence(artifacts),
|
|
474
|
+
qc: buildQcEvidence(artifacts),
|
|
475
|
+
validation: buildValidationEvidence(artifacts.workerResultContracts),
|
|
476
|
+
tokens: buildTokenEvidence(artifacts),
|
|
477
|
+
intervention: buildInterventionEvidence(artifacts),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOL history store.
|
|
4
|
+
*
|
|
5
|
+
* Persists SolScoreReport snapshots as append-only JSONL under a
|
|
6
|
+
* deterministic local artifact path. Each line is a self-contained
|
|
7
|
+
* snapshot enriched with grouping keys from SolEvidence.
|
|
8
|
+
*
|
|
9
|
+
* Design rules:
|
|
10
|
+
* - Append-only: never rewrites or deletes historical entries.
|
|
11
|
+
* - Deterministic path: `.polaris/sol-history/scores.jsonl` relative to repo root.
|
|
12
|
+
* - Each snapshot includes run_id, cluster_id, scored_at, grouping_keys,
|
|
13
|
+
* and the full SolScoreReport for complete reproducibility.
|
|
14
|
+
* - No remote analytics — everything stays local.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.getHistoryDir = getHistoryDir;
|
|
18
|
+
exports.getHistoryFilePath = getHistoryFilePath;
|
|
19
|
+
exports.appendSnapshot = appendSnapshot;
|
|
20
|
+
exports.loadSnapshots = loadSnapshots;
|
|
21
|
+
exports.buildSnapshot = buildSnapshot;
|
|
22
|
+
const node_fs_1 = require("node:fs");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
// ──────────────────────────────────────────────
|
|
25
|
+
// Store path
|
|
26
|
+
// ──────────────────────────────────────────────
|
|
27
|
+
const DEFAULT_HISTORY_DIR = ".polaris/sol-history";
|
|
28
|
+
const SCORES_FILE = "scores.jsonl";
|
|
29
|
+
function getHistoryDir(repoRoot, customPath) {
|
|
30
|
+
return (0, node_path_1.join)(repoRoot, customPath ?? DEFAULT_HISTORY_DIR);
|
|
31
|
+
}
|
|
32
|
+
function getHistoryFilePath(repoRoot, customPath) {
|
|
33
|
+
return (0, node_path_1.join)(getHistoryDir(repoRoot, customPath), SCORES_FILE);
|
|
34
|
+
}
|
|
35
|
+
// ──────────────────────────────────────────────
|
|
36
|
+
// Write (append-only)
|
|
37
|
+
// ──────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Persist a SOL score snapshot. Appends one JSONL line to the history file.
|
|
40
|
+
* Creates the directory and file if they don't exist.
|
|
41
|
+
*
|
|
42
|
+
* @returns The path the snapshot was written to.
|
|
43
|
+
*/
|
|
44
|
+
function appendSnapshot(repoRoot, snapshot, customPath) {
|
|
45
|
+
const dir = getHistoryDir(repoRoot, customPath);
|
|
46
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
47
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
48
|
+
const filePath = (0, node_path_1.join)(dir, SCORES_FILE);
|
|
49
|
+
const line = JSON.stringify(snapshot) + "\n";
|
|
50
|
+
(0, node_fs_1.appendFileSync)(filePath, line, "utf-8");
|
|
51
|
+
return filePath;
|
|
52
|
+
}
|
|
53
|
+
// ──────────────────────────────────────────────
|
|
54
|
+
// Read
|
|
55
|
+
// ──────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* Load all snapshots from the history file.
|
|
58
|
+
* Returns an empty array when the file doesn't exist.
|
|
59
|
+
*/
|
|
60
|
+
function loadSnapshots(repoRoot, customPath) {
|
|
61
|
+
const filePath = getHistoryFilePath(repoRoot, customPath);
|
|
62
|
+
if (!(0, node_fs_1.existsSync)(filePath))
|
|
63
|
+
return [];
|
|
64
|
+
const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
65
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
66
|
+
const snapshots = [];
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
try {
|
|
69
|
+
snapshots.push(JSON.parse(line));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Skip malformed lines — append-only store tolerates partial writes
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return snapshots;
|
|
76
|
+
}
|
|
77
|
+
// ──────────────────────────────────────────────
|
|
78
|
+
// Build snapshot from report + evidence metadata
|
|
79
|
+
// ──────────────────────────────────────────────
|
|
80
|
+
/**
|
|
81
|
+
* Build a SolScoreSnapshot from a report and evidence metadata.
|
|
82
|
+
*/
|
|
83
|
+
function buildSnapshot(report, groupingKeys, workerIds) {
|
|
84
|
+
return {
|
|
85
|
+
schema_version: "1.0",
|
|
86
|
+
report,
|
|
87
|
+
grouping_keys: groupingKeys,
|
|
88
|
+
worker_ids: workerIds,
|
|
89
|
+
};
|
|
90
|
+
}
|