@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.
@@ -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
+ }