@obsunified/collector 2.2.3 → 2.4.3

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.
Files changed (58) hide show
  1. package/dist/auth.d.ts +2 -1
  2. package/dist/{chunk-ZSF5XP3B.js → chunk-RASSTGNB.js} +30 -22
  3. package/dist/chunk-RASSTGNB.js.map +1 -0
  4. package/dist/index.d.ts +264 -62
  5. package/dist/index.js +3390 -2350
  6. package/dist/index.js.map +1 -1
  7. package/dist/{ingest-auth-C5-8Mly5.d.ts → ingest-auth-BTowieER.d.ts} +96 -1
  8. package/dist/{openai-GSJZGFGL.js → openai-VDKVVZAV.js} +2 -2
  9. package/package.json +4 -2
  10. package/src/__tests__/autosre-closed-loop.test.ts +110 -0
  11. package/src/__tests__/db-parity.integration.test.ts +961 -0
  12. package/src/__tests__/internal-contracts-routes.test.ts +451 -0
  13. package/src/framework/env.ts +113 -0
  14. package/src/index.ts +27 -4
  15. package/src/lib/ai-store/evaluations.test.ts +3 -3
  16. package/src/lib/alert-email.ts +104 -0
  17. package/src/lib/alerts-store.test.ts +116 -2
  18. package/src/lib/alerts-store.ts +421 -57
  19. package/src/lib/analyses-store.test.ts +6 -6
  20. package/src/lib/db-parity.ts +883 -0
  21. package/src/lib/eval-cases-store.test.ts +1 -1
  22. package/src/lib/eval-cases-store.ts +16 -4
  23. package/src/lib/evidence-references.ts +30 -22
  24. package/src/lib/metric-alerts.test.ts +769 -0
  25. package/src/lib/metric-alerts.ts +640 -0
  26. package/src/lib/metrics-store.ts +9 -2
  27. package/src/lib/sql-db-postgres.integration.test.ts +247 -0
  28. package/src/lib/stage6.test.ts +1 -1
  29. package/src/lib/store/trace-queries.ts +1 -1
  30. package/src/migrations/054_alert_rules_pending_secs.sql +11 -0
  31. package/src/migrations/055_alert_state_pending_since.sql +12 -0
  32. package/src/migrations/056_metric_point_series_received.sql +9 -0
  33. package/src/migrations/057_alert_evaluations_outcome.sql +11 -0
  34. package/src/migrations/058_alert_state_pending_seen_at.sql +12 -0
  35. package/src/migrations/059_alert_evaluations_error.sql +11 -0
  36. package/src/migrations/parity.test.ts +105 -3
  37. package/src/migrations-postgres/054_alert_rules_pending_secs.sql +7 -0
  38. package/src/migrations-postgres/055_alert_state_pending_since.sql +6 -0
  39. package/src/migrations-postgres/056_metric_point_series_received.sql +7 -0
  40. package/src/migrations-postgres/057_alert_evaluations_outcome.sql +6 -0
  41. package/src/migrations-postgres/058_alert_state_pending_seen_at.sql +6 -0
  42. package/src/migrations-postgres/059_alert_evaluations_error.sql +6 -0
  43. package/src/migrations-postgres/README.md +13 -0
  44. package/src/plugins/ai-receiver.ts +6 -1
  45. package/src/plugins/alerts-evaluator-pending.test.ts +322 -0
  46. package/src/plugins/alerts-evaluator-retention.test.ts +2 -0
  47. package/src/plugins/alerts-evaluator.test.ts +10 -2
  48. package/src/plugins/alerts-evaluator.ts +239 -40
  49. package/src/plugins/alerts-routes.ts +17 -2
  50. package/src/plugins/autosre-routes.ts +251 -140
  51. package/src/plugins/connected-routes/manifest.test.ts +114 -32
  52. package/src/plugins/connected-routes/manifest.ts +59 -76
  53. package/src/plugins/connected-routes.test.ts +10 -8
  54. package/src/plugins/connected-routes.ts +42 -12
  55. package/src/plugins/eval-cases-routes.ts +33 -10
  56. package/src/plugins/evidence-retrieval-routes.ts +60 -25
  57. package/dist/chunk-ZSF5XP3B.js.map +0 -1
  58. /package/dist/{openai-GSJZGFGL.js.map → openai-VDKVVZAV.js.map} +0 -0
@@ -3,7 +3,10 @@ import type {
3
3
  EvidenceReferenceContract as EvidenceReferenceContractType,
4
4
  JsonValue,
5
5
  } from "@obsunified/types";
6
- import { EVIDENCE_REFERENCE_CONTRACT } from "@obsunified/types";
6
+ import {
7
+ dashboardRouteToHref,
8
+ EVIDENCE_REFERENCE_CONTRACT,
9
+ } from "@obsunified/types";
7
10
  import type { CollectorPlugin } from "../framework/collector";
8
11
  import {
9
12
  type EvalCase,
@@ -20,6 +23,7 @@ import { randomHex } from "../lib/hash";
20
23
  import { sqlDbFor } from "../lib/sql-db";
21
24
  import { getProjectId } from "./_context";
22
25
  import { readJsonObject } from "./_json";
26
+ import { respondWithContract } from "./autosre-routes";
23
27
 
24
28
  interface EvalCaseRequestBody {
25
29
  sourceEntityType?: unknown;
@@ -122,7 +126,10 @@ const evalCaseResultEvidenceReferences = (
122
126
  sourceLabel: `Eval case result "${resultId}"`,
123
127
  sourceId: resultId,
124
128
  sourceKind: "eval_case_result",
125
- sourceRoute: `#/evaluations?case=${encodeURIComponent(evalCase.id)}&result=${encodeURIComponent(resultId)}`,
129
+ sourceRoute: dashboardRouteToHref({
130
+ tab: "evaluations",
131
+ evaluationId: evalCase.id,
132
+ }),
126
133
  sourceName: evalCase.name,
127
134
  },
128
135
  evalCase,
@@ -189,7 +196,12 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
189
196
  const store = new EvalCasesStore(sqlDbFor(c.env));
190
197
  try {
191
198
  const evalCase = await store.createCase(getProjectId(c), input);
192
- return c.json({ evalCase }, 201);
199
+ return respondWithContract(
200
+ c,
201
+ "POST /internal/eval-cases",
202
+ { evalCase },
203
+ 201,
204
+ );
193
205
  } catch (err) {
194
206
  if (err instanceof EvalCaseSourceNotFoundError) {
195
207
  return c.json(
@@ -223,7 +235,7 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
223
235
  sourceEntityId: c.req.query("sourceEntityId"),
224
236
  limit: parseLimit(c.req.query("limit")),
225
237
  });
226
- return c.json({ evalCases });
238
+ return respondWithContract(c, "GET /internal/eval-cases", { evalCases });
227
239
  });
228
240
 
229
241
  app.get("/internal/eval-cases/:id", async (c) => {
@@ -237,7 +249,9 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
237
249
  404,
238
250
  );
239
251
  }
240
- return c.json({ evalCase });
252
+ return respondWithContract(c, "GET /internal/eval-cases/:id", {
253
+ evalCase,
254
+ });
241
255
  });
242
256
 
243
257
  app.post("/internal/eval-runs", async (c) => {
@@ -349,7 +363,12 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
349
363
  const store = new EvalCasesStore(sqlDbFor(c.env));
350
364
  try {
351
365
  const evalRun = await store.createRun(getProjectId(c), input);
352
- return c.json({ evalRun }, 201);
366
+ return respondWithContract(
367
+ c,
368
+ "POST /internal/eval-runs",
369
+ { evalRun },
370
+ 201,
371
+ );
353
372
  } catch (err) {
354
373
  const message = err instanceof Error ? err.message : String(err);
355
374
  if (message.includes("does not reference a case")) {
@@ -377,7 +396,7 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
377
396
  status,
378
397
  limit: parseLimit(c.req.query("limit")),
379
398
  });
380
- return c.json({ evalRuns });
399
+ return respondWithContract(c, "GET /internal/eval-runs", { evalRuns });
381
400
  });
382
401
 
383
402
  app.get("/internal/eval-runs/:id", async (c) => {
@@ -391,7 +410,7 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
391
410
  404,
392
411
  );
393
412
  }
394
- return c.json({ evalRun });
413
+ return respondWithContract(c, "GET /internal/eval-runs/:id", { evalRun });
395
414
  });
396
415
 
397
416
  app.post("/internal/eval-cases/:id/results", async (c) => {
@@ -503,7 +522,9 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
503
522
  evidenceContract: EVIDENCE_REFERENCE_CONTRACT,
504
523
  };
505
524
 
506
- return c.json(
525
+ return respondWithContract(
526
+ c,
527
+ "POST /internal/eval-cases/:id/results",
507
528
  {
508
529
  evalCaseResult,
509
530
  evalRun: refreshedRun,
@@ -569,7 +590,9 @@ export const evalCasesRoutesPlugin: CollectorPlugin = {
569
590
  }),
570
591
  );
571
592
 
572
- return c.json({ evalCaseResults });
593
+ return respondWithContract(c, "GET /internal/eval-cases/:id/results", {
594
+ evalCaseResults,
595
+ });
573
596
  });
574
597
  },
575
598
  };
@@ -11,7 +11,10 @@ import type {
11
11
  TelemetrySpanDetail,
12
12
  TelemetryTraceDetailResponse,
13
13
  } from "@obsunified/types";
14
- import { buildFindingFingerprint } from "@obsunified/types";
14
+ import {
15
+ buildFindingFingerprint,
16
+ dashboardRouteToHref,
17
+ } from "@obsunified/types";
15
18
  import type { CollectorPlugin } from "../framework/collector";
16
19
  import {
17
20
  computeChangeBlastRadius,
@@ -41,6 +44,7 @@ import { LogsStore } from "../lib/logs-store";
41
44
  import { decodePprofBlob, summarizeProfileFrames } from "../lib/parse-pprof";
42
45
  import type { SqlDb } from "../lib/sql-db";
43
46
  import { getProjectId } from "./_context";
47
+ import { respondWithContract } from "./autosre-routes";
44
48
  import { fetchReplayChunks } from "./replay-query-routes";
45
49
 
46
50
  interface EvidenceRefPayload {
@@ -158,24 +162,35 @@ const estimateTokens = (value: unknown): number =>
158
162
  Math.ceil(JSON.stringify(value).length / 4);
159
163
 
160
164
  const traceRoute = (traceId: string) =>
161
- `#/traces/${encodeURIComponent(traceId)}`;
165
+ dashboardRouteToHref({ tab: "traces", traceId });
162
166
  const spanRoute = (traceId: string, spanId: string) =>
163
- `${traceRoute(traceId)}#span=${encodeURIComponent(spanId)}`;
164
- const logRoute = (logId: string) => `#/logs?id=${encodeURIComponent(logId)}`;
167
+ dashboardRouteToHref({ tab: "traces", traceId, spanId });
168
+ const logRoute = (logId: string) =>
169
+ dashboardRouteToHref({ tab: "logs", logId });
165
170
  const actionRoute = (actionId: string) =>
166
- `#/actions/${encodeURIComponent(actionId)}`;
171
+ dashboardRouteToHref({ tab: "actions", actionId });
167
172
  const agentRunRoute = (agentRunId: string) =>
168
- `#/agent-runs/${encodeURIComponent(agentRunId)}`;
173
+ dashboardRouteToHref({ tab: "agent-runs", agentRunId });
169
174
  const toolCallRoute = (toolCallId: string) =>
170
- `#/tool-calls/${encodeURIComponent(toolCallId)}`;
175
+ dashboardRouteToHref({ tab: "tool-calls", toolCallId });
171
176
  const replayRoute = (sessionId: string) =>
172
- `#/replay?session=${encodeURIComponent(sessionId)}`;
177
+ dashboardRouteToHref({ tab: "replay", sessionId });
173
178
  const profileRoute = (profileId: string, traceId?: string) =>
174
- `#/profiles/${encodeURIComponent(profileId)}${
175
- traceId ? `?trace_id=${encodeURIComponent(traceId)}` : ""
176
- }`;
179
+ dashboardRouteToHref({
180
+ tab: "profiles",
181
+ profileId,
182
+ ...(traceId ? { traceId } : {}),
183
+ });
177
184
  const aiCallRoute = (callId: string) =>
178
- `#/ai/calls/${encodeURIComponent(callId)}`;
185
+ dashboardRouteToHref({ tab: "ai", aiCallId: callId });
186
+ const evalRoute = (evaluationId: string) =>
187
+ dashboardRouteToHref({ tab: "evaluations", evaluationId });
188
+ const changeEntityRoute = (changeId: string) =>
189
+ dashboardRouteToHref({ tab: "changes", changeId });
190
+ const findingEntityRoute = (findingId: string) =>
191
+ dashboardRouteToHref({ tab: "findings", findingId });
192
+ const incidentEntityRoute = (incidentId: string) =>
193
+ dashboardRouteToHref({ tab: "incidents", incidentId });
179
194
 
180
195
  const evidenceRef = (
181
196
  evidenceId: string,
@@ -1069,7 +1084,7 @@ const actionEvidenceReferences = (
1069
1084
  `eval:${evalResult.id}`,
1070
1085
  "eval",
1071
1086
  evalResult.id,
1072
- `#/evals/${encodeURIComponent(evalResult.id)}`,
1087
+ evalRoute(evalResult.id),
1073
1088
  "evidence.bundle.eval_results",
1074
1089
  evalResult.passed ? 0.75 : 0.9,
1075
1090
  `Eval ${evalResult.evaluatorName} ${evalResult.passed ? "passed" : "failed"}${evalResult.score === null ? "" : ` with score ${evalResult.score}`}.`,
@@ -1651,7 +1666,7 @@ const attachAutoSreTriageContext = async (
1651
1666
 
1652
1667
  for (const br of blastRadii) {
1653
1668
  const changeId = br.change.id;
1654
- const changeRoute = `#/changes/${encodeURIComponent(changeId)}`;
1669
+ const changeRoute = changeEntityRoute(changeId);
1655
1670
  bundle.evidenceReferences.push({
1656
1671
  evidenceId: `change:${changeId}`,
1657
1672
  entityKind: "change",
@@ -1685,7 +1700,7 @@ const attachAutoSreTriageContext = async (
1685
1700
  }
1686
1701
 
1687
1702
  for (const finding of priorFindingsList) {
1688
- const findingRoute = `#/findings/${encodeURIComponent(finding.id)}`;
1703
+ const findingRoute = findingEntityRoute(finding.id);
1689
1704
  bundle.evidenceReferences.push({
1690
1705
  evidenceId: `finding:${finding.id}`,
1691
1706
  entityKind: "finding",
@@ -1897,7 +1912,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
1897
1912
  runtime.logger,
1898
1913
  );
1899
1914
  }
1900
- return c.json(await materializeBundle(db, projectId, bundle));
1915
+ return respondWithContract(
1916
+ c,
1917
+ "POST /internal/evidence/bundle",
1918
+ await materializeBundle(db, projectId, bundle),
1919
+ );
1901
1920
  }
1902
1921
 
1903
1922
  if (anchor.entityKind === "incident") {
@@ -1919,7 +1938,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
1919
1938
  { kind: incident.symptomAnchorKind, id: incident.symptomAnchorId },
1920
1939
  );
1921
1940
 
1922
- const incRoute = `#/incidents/${encodeURIComponent(incident.id)}`;
1941
+ const incRoute = incidentEntityRoute(incident.id);
1923
1942
  const baseBundle: EvidenceBundle = {
1924
1943
  schemaVersion: BUNDLE_SCHEMA_VERSION,
1925
1944
  intent,
@@ -1988,7 +2007,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
1988
2007
  baseBundle,
1989
2008
  runtime.logger,
1990
2009
  );
1991
- return c.json(await materializeBundle(db, projectId, enriched));
2010
+ return respondWithContract(
2011
+ c,
2012
+ "POST /internal/evidence/bundle",
2013
+ await materializeBundle(db, projectId, enriched),
2014
+ );
1992
2015
  }
1993
2016
 
1994
2017
  if (anchor.entityKind === "change") {
@@ -2012,7 +2035,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2012
2035
  null,
2013
2036
  );
2014
2037
 
2015
- const changeRoute = `#/changes/${encodeURIComponent(change.id)}`;
2038
+ const changeRoute = changeEntityRoute(change.id);
2016
2039
  const summary =
2017
2040
  blastRadius?.summary ??
2018
2041
  `Change ${change.id} (${change.kind} ${change.version ?? ""}) in ${change.environment}: ${change.summary}`;
@@ -2101,7 +2124,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2101
2124
  baseBundle,
2102
2125
  runtime.logger,
2103
2126
  );
2104
- return c.json(await materializeBundle(db, projectId, enriched));
2127
+ return respondWithContract(
2128
+ c,
2129
+ "POST /internal/evidence/bundle",
2130
+ await materializeBundle(db, projectId, enriched),
2131
+ );
2105
2132
  }
2106
2133
 
2107
2134
  if (anchor.entityKind === "finding") {
@@ -2129,7 +2156,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2129
2156
  { kind: finding.symptomEntityKind, id: finding.symptomEntityId },
2130
2157
  );
2131
2158
 
2132
- const findingRoute = `#/findings/${encodeURIComponent(finding.id)}`;
2159
+ const findingRoute = findingEntityRoute(finding.id);
2133
2160
  const summary = `${finding.title} (${finding.causalProofTier}, verification=${finding.causalVerificationStatus}, recovery=${finding.recoveryStatus}): ${finding.summary}`;
2134
2161
  const baseBundle: EvidenceBundle = {
2135
2162
  schemaVersion: BUNDLE_SCHEMA_VERSION,
@@ -2219,7 +2246,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2219
2246
  baseBundle,
2220
2247
  runtime.logger,
2221
2248
  );
2222
- return c.json(await materializeBundle(db, projectId, enriched));
2249
+ return respondWithContract(
2250
+ c,
2251
+ "POST /internal/evidence/bundle",
2252
+ await materializeBundle(db, projectId, enriched),
2253
+ );
2223
2254
  }
2224
2255
 
2225
2256
  if (anchor.entityKind !== "trace") {
@@ -2266,7 +2297,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2266
2297
  runtime.logger,
2267
2298
  );
2268
2299
  }
2269
- return c.json(await materializeBundle(db, projectId, bundle));
2300
+ return respondWithContract(
2301
+ c,
2302
+ "POST /internal/evidence/bundle",
2303
+ await materializeBundle(db, projectId, bundle),
2304
+ );
2270
2305
  });
2271
2306
 
2272
2307
  app.get("/internal/evidence/stats", async (c) => {
@@ -2358,7 +2393,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2358
2393
  expanded_at: string;
2359
2394
  }>(),
2360
2395
  ]);
2361
- return c.json({
2396
+ return respondWithContract(c, "GET /internal/evidence/stats", {
2362
2397
  projectId,
2363
2398
  generatedAt: new Date().toISOString(),
2364
2399
  byKind: byKind.results.map((row) => ({
@@ -2396,7 +2431,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
2396
2431
  })),
2397
2432
  });
2398
2433
  } catch {
2399
- return c.json({
2434
+ return respondWithContract(c, "GET /internal/evidence/stats", {
2400
2435
  projectId,
2401
2436
  generatedAt: new Date().toISOString(),
2402
2437
  byKind: [],
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/evidence-references.ts","../src/lib/llm.ts"],"sourcesContent":["import type {\n\tAIEvaluationRecord,\n\tAlertEvaluation,\n\tAlertRule,\n\tAnalysisResult,\n\tAskEvidence,\n\tEvidenceCitation,\n\tEvidenceEntityKind,\n\tEvidenceNextPivot,\n\tEvidenceReference,\n\tJsonValue,\n} from \"@obsunified/types\";\n\nconst analysisRoute = (analysisId: string) =>\n\t`#/investigate/${encodeURIComponent(analysisId)}`;\n\nexport const analysisEvidenceReference = (\n\tevidence: AskEvidence,\n): EvidenceReference => {\n\tconst route = analysisRoute(evidence.analysisId);\n\tconst status = evidence.result?.status ?? \"unknown\";\n\treturn {\n\t\tevidenceId: `ask:analysis:${evidence.analysisId}`,\n\t\tentityKind: \"analysis\",\n\t\tentityId: evidence.analysisId,\n\t\troute,\n\t\tsource: \"ask.run_analysis\",\n\t\tconfidence: evidence.result ? 1 : 0.55,\n\t\treason: evidence.result\n\t\t\t? `Ask consulted analysis \"${evidence.definition.title}\" with latest status ${status}.`\n\t\t\t: `Ask consulted analysis \"${evidence.definition.title}\", but no latest result was available.`,\n\t\tcitations: [\n\t\t\t{\n\t\t\t\tlabel: evidence.definition.title,\n\t\t\t\tentityKind: \"analysis\",\n\t\t\t\tentityId: evidence.analysisId,\n\t\t\t\troute,\n\t\t\t},\n\t\t],\n\t\tsuggestedNextPivots: [\n\t\t\t{\n\t\t\t\tlabel: \"Open investigation\",\n\t\t\t\tentityKind: \"analysis\",\n\t\t\t\tentityId: evidence.analysisId,\n\t\t\t\troute,\n\t\t\t\treason:\n\t\t\t\t\t\"Review the analysis payload, narrative, and connected telemetry rail.\",\n\t\t\t},\n\t\t],\n\t};\n};\n\nexport const askEvidenceReferences = (\n\tevidence: Iterable<AskEvidence>,\n): EvidenceReference[] =>\n\t[...evidence].map((item) => analysisEvidenceReference(item));\n\nconst MAX_ANALYSIS_EVIDENCE_REFERENCES = 50;\n\ninterface EvidenceTable {\n\ttitle?: string | null;\n\theaders: string[];\n\trows: JsonValue[][];\n}\n\ninterface RowEntity {\n\tentityKind: EvidenceEntityKind;\n\tentityId: string;\n\troute: string;\n\tlabel: string;\n\tconfidence: number;\n\tcitations: EvidenceCitation[];\n\tsuggestedNextPivots: EvidenceNextPivot[];\n}\n\nconst isRecord = (value: unknown): value is Record<string, JsonValue> =>\n\t!!value && typeof value === \"object\" && !Array.isArray(value);\n\nconst asString = (value: JsonValue | undefined): string | null => {\n\tif (typeof value === \"string\" && value.trim()) return value.trim();\n\tif (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n\treturn null;\n};\n\nconst normalizeHeader = (header: string) =>\n\theader\n\t\t.trim()\n\t\t.toLowerCase()\n\t\t.replace(/[\\s-]+/g, \"_\");\n\nconst valueFor = (\n\trow: JsonValue[],\n\theaderIndex: Map<string, number>,\n\tnames: string[],\n): string | null => {\n\tfor (const name of names) {\n\t\tconst index = headerIndex.get(name);\n\t\tif (index === undefined) continue;\n\t\tconst value = asString(row[index]);\n\t\tif (value) return value;\n\t}\n\treturn null;\n};\n\nconst traceRoute = (traceId: string) =>\n\t`#/traces/${encodeURIComponent(traceId)}`;\n\nconst spanRoute = (traceId: string, spanId: string) =>\n\t`${traceRoute(traceId)}#span=${encodeURIComponent(spanId)}`;\n\nexport const evidenceRouteFor = (\n\tentityKind: EvidenceEntityKind,\n\tentityId: string,\n\ttraceId?: string | null,\n) => {\n\tswitch (entityKind) {\n\t\tcase \"analysis\":\n\t\t\treturn analysisRoute(entityId);\n\t\tcase \"action\":\n\t\t\treturn `#/actions/${encodeURIComponent(entityId)}`;\n\t\tcase \"alert\":\n\t\t\treturn `#/alerts?alert=${encodeURIComponent(entityId)}`;\n\t\tcase \"agent_run\":\n\t\t\treturn `#/agent-runs/${encodeURIComponent(entityId)}`;\n\t\tcase \"ai_call\":\n\t\t\treturn `#/ai/calls/${encodeURIComponent(entityId)}`;\n\t\tcase \"eval\":\n\t\t\treturn `#/evals/${encodeURIComponent(entityId)}`;\n\t\tcase \"log\":\n\t\t\treturn `#/logs?id=${encodeURIComponent(entityId)}`;\n\t\tcase \"profile\":\n\t\t\treturn traceId\n\t\t\t\t? `#/profiles/${encodeURIComponent(entityId)}?trace_id=${encodeURIComponent(traceId)}`\n\t\t\t\t: `#/profiles/${encodeURIComponent(entityId)}`;\n\t\tcase \"replay\":\n\t\t\treturn `#/replay?session=${encodeURIComponent(entityId)}`;\n\t\tcase \"service\":\n\t\t\treturn `#/traces?service=${encodeURIComponent(entityId)}`;\n\t\tcase \"span\": {\n\t\t\tconst [tracePart, spanPart] = entityId.split(\":\");\n\t\t\treturn spanPart ? spanRoute(tracePart, spanPart) : `#/spans/${entityId}`;\n\t\t}\n\t\tcase \"tool_call\":\n\t\t\treturn `#/tool-calls/${encodeURIComponent(entityId)}`;\n\t\tcase \"trace\":\n\t\t\treturn traceRoute(entityId);\n\t\tcase \"docs\":\n\t\t\treturn entityId.startsWith(\"#/\") ? entityId : `#/docs/${entityId}`;\n\t\tcase \"change\":\n\t\t\treturn `#/changes/${encodeURIComponent(entityId)}`;\n\t\tcase \"finding\":\n\t\t\treturn `#/findings/${encodeURIComponent(entityId)}`;\n\t\tcase \"incident\":\n\t\t\treturn `#/incidents/${encodeURIComponent(entityId)}`;\n\t}\n\tconst exhaustive: never = entityKind;\n\treturn exhaustive;\n};\n\nconst parseEvidenceTables = (\n\tpayload: Record<string, unknown>,\n): Array<{ sectionName: string; table: EvidenceTable }> => {\n\tconst evidence = payload.evidence;\n\tif (!isRecord(evidence)) return [];\n\n\tconst tables: Array<{ sectionName: string; table: EvidenceTable }> = [];\n\tfor (const [sectionName, section] of Object.entries(evidence)) {\n\t\tif (!isRecord(section)) continue;\n\t\tconst headers = section.headers;\n\t\tconst rows = section.rows;\n\t\tif (\n\t\t\t!Array.isArray(headers) ||\n\t\t\t!headers.every(\n\t\t\t\t(header): header is string => typeof header === \"string\",\n\t\t\t) ||\n\t\t\t!Array.isArray(rows)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst normalizedRows = rows.filter((row): row is JsonValue[] =>\n\t\t\tArray.isArray(row),\n\t\t);\n\t\ttables.push({\n\t\t\tsectionName,\n\t\t\ttable: {\n\t\t\t\ttitle: asString(section.title),\n\t\t\t\theaders,\n\t\t\t\trows: normalizedRows,\n\t\t\t},\n\t\t});\n\t}\n\treturn tables;\n};\n\nconst instrumentationGapEvidenceReferences = (\n\tresult: Pick<AnalysisResult, \"analysisId\" | \"payload\">,\n): EvidenceReference[] => {\n\tconst gaps = result.payload.instrumentationGaps;\n\tif (!isRecord(gaps)) return [];\n\tconst traceId = asString(gaps.traceId);\n\tconst blindspots = gaps.blindspots;\n\tif (!traceId || !Array.isArray(blindspots)) return [];\n\n\treturn blindspots\n\t\t.filter(isRecord)\n\t\t.map((gap, index): EvidenceReference | null => {\n\t\t\tconst spanId = asString(gap.parentSpanId);\n\t\t\tif (!spanId) return null;\n\t\t\tconst serviceName = asString(gap.parentServiceName);\n\t\t\tconst spanName = asString(gap.parentSpanName);\n\t\t\tconst durationMs =\n\t\t\t\ttypeof gap.durationMs === \"number\" && Number.isFinite(gap.durationMs)\n\t\t\t\t\t? gap.durationMs\n\t\t\t\t\t: null;\n\t\t\tconst ratio =\n\t\t\t\ttypeof gap.ratioOfParent === \"number\" &&\n\t\t\t\tNumber.isFinite(gap.ratioOfParent)\n\t\t\t\t\t? gap.ratioOfParent\n\t\t\t\t\t: null;\n\t\t\tconst recommendation = asString(gap.recommendation);\n\t\t\tconst thresholdVersion =\n\t\t\t\tasString(gap.thresholdVersion) ??\n\t\t\t\t(isRecord(gaps.thresholds) ? asString(gaps.thresholds.version) : null);\n\t\t\tconst entityId = `${traceId}:${spanId}`;\n\t\t\tconst route = spanRoute(traceId, spanId);\n\t\t\tconst label = spanName ? `${spanName} (${spanId})` : `span ${spanId}`;\n\t\t\tconst durationText =\n\t\t\t\tdurationMs === null ? \"unknown duration\" : `${durationMs}ms`;\n\t\t\tconst ratioText =\n\t\t\t\tratio === null ? \"\" : `, ${(ratio * 100).toFixed(1)}% of parent`;\n\t\t\tconst thresholdText = thresholdVersion\n\t\t\t\t? ` Thresholds: ${thresholdVersion}.`\n\t\t\t\t: \"\";\n\t\t\tconst docsRoute = \"#/docs/howto/ebpf\";\n\t\t\treturn {\n\t\t\t\tevidenceId: `analysis:${result.analysisId}:instrumentation-gap:${index}:span:${entityId}`,\n\t\t\t\tentityKind: \"span\",\n\t\t\t\tentityId,\n\t\t\t\troute,\n\t\t\t\tsource: \"analysis.payload.instrumentationGaps\",\n\t\t\t\tconfidence: 0.9,\n\t\t\t\treason: `Analysis \"${result.analysisId}\" found uninstrumented self-time on ${label}: ${durationText}${ratioText}.${thresholdText}${recommendation ? ` ${recommendation}` : \"\"}`,\n\t\t\t\tcitations: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: `trace ${traceId}`,\n\t\t\t\t\t\tentityKind: \"trace\",\n\t\t\t\t\t\tentityId: traceId,\n\t\t\t\t\t\troute: traceRoute(traceId),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tsuggestedNextPivots: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Open uninstrumented span\",\n\t\t\t\t\t\tentityKind: \"span\",\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\"Inspect the parent span whose self-time suggests missing child instrumentation.\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Open trace gap data\",\n\t\t\t\t\t\tentityKind: \"trace\",\n\t\t\t\t\t\tentityId: traceId,\n\t\t\t\t\t\troute: `${traceRoute(traceId)}/gaps`,\n\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\"Review all structured instrumentation blindspots for this trace.\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Open profiler setup docs\",\n\t\t\t\t\t\tentityKind: \"docs\",\n\t\t\t\t\t\tentityId: \"howto/ebpf\",\n\t\t\t\t\t\troute: docsRoute,\n\t\t\t\t\t\treason:\n\t\t\t\t\t\t\tserviceName != null\n\t\t\t\t\t\t\t\t? `Use profiler or eBPF setup guidance to cover missing spans in ${serviceName}.`\n\t\t\t\t\t\t\t\t: \"Use profiler or eBPF setup guidance to cover missing spans.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t})\n\t\t.filter((ref): ref is EvidenceReference => ref !== null)\n\t\t.slice(0, MAX_ANALYSIS_EVIDENCE_REFERENCES);\n};\n\nconst entityFromRow = (\n\trow: JsonValue[],\n\theaders: string[],\n): RowEntity | null => {\n\tconst headerIndex = new Map(headers.map((header, index) => [header, index]));\n\tconst traceId = valueFor(row, headerIndex, [\"trace_id\", \"traceid\", \"trace\"]);\n\tconst spanId = valueFor(row, headerIndex, [\"span_id\", \"spanid\"]);\n\tconst logId = valueFor(row, headerIndex, [\"log_id\", \"logid\"]);\n\tconst profileId = valueFor(row, headerIndex, [\"profile_id\", \"profileid\"]);\n\tconst actionId = valueFor(row, headerIndex, [\"action_id\", \"actionid\"]);\n\tconst agentRunId = valueFor(row, headerIndex, [\n\t\t\"agent_run_id\",\n\t\t\"agentrunid\",\n\t\t\"agent_run\",\n\t]);\n\tconst toolCallId = valueFor(row, headerIndex, [\n\t\t\"tool_call_id\",\n\t\t\"toolcallid\",\n\t\t\"tool_call\",\n\t]);\n\tconst evalId = valueFor(row, headerIndex, [\"eval_id\", \"evalid\"]);\n\tconst service = valueFor(row, headerIndex, [\"service\", \"service_name\"]);\n\n\tconst traceCitation = traceId\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tlabel: `trace ${traceId}`,\n\t\t\t\t\tentityKind: \"trace\" as const,\n\t\t\t\t\tentityId: traceId,\n\t\t\t\t\troute: traceRoute(traceId),\n\t\t\t\t},\n\t\t\t]\n\t\t: [];\n\n\tif (traceId && spanId) {\n\t\tconst entityId = `${traceId}:${spanId}`;\n\t\treturn {\n\t\t\tentityKind: \"span\",\n\t\t\tentityId,\n\t\t\troute: spanRoute(traceId, spanId),\n\t\t\tlabel: `span ${spanId}`,\n\t\t\tconfidence: 0.95,\n\t\t\tcitations: traceCitation,\n\t\t\tsuggestedNextPivots: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Open span\",\n\t\t\t\t\tentityKind: \"span\",\n\t\t\t\t\tentityId,\n\t\t\t\t\troute: spanRoute(traceId, spanId),\n\t\t\t\t\treason: \"Inspect the cited span in its trace context.\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Open trace\",\n\t\t\t\t\tentityKind: \"trace\",\n\t\t\t\t\tentityId: traceId,\n\t\t\t\t\troute: traceRoute(traceId),\n\t\t\t\t\treason: \"Review the surrounding trace context.\",\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\tconst candidates: Array<{\n\t\tentityKind: EvidenceEntityKind;\n\t\tentityId: string | null;\n\t\tlabelPrefix: string;\n\t\tconfidence: number;\n\t}> = [\n\t\t{\n\t\t\tentityKind: \"trace\",\n\t\t\tentityId: traceId,\n\t\t\tlabelPrefix: \"trace\",\n\t\t\tconfidence: 0.95,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"action\",\n\t\t\tentityId: actionId,\n\t\t\tlabelPrefix: \"action\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"tool_call\",\n\t\t\tentityId: toolCallId,\n\t\t\tlabelPrefix: \"tool call\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"agent_run\",\n\t\t\tentityId: agentRunId,\n\t\t\tlabelPrefix: \"agent run\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"profile\",\n\t\t\tentityId: profileId,\n\t\t\tlabelPrefix: \"profile\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"log\",\n\t\t\tentityId: logId,\n\t\t\tlabelPrefix: \"log\",\n\t\t\tconfidence: 0.85,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"eval\",\n\t\t\tentityId: evalId,\n\t\t\tlabelPrefix: \"eval\",\n\t\t\tconfidence: 0.85,\n\t\t},\n\t\t{\n\t\t\tentityKind: \"service\",\n\t\t\tentityId: service,\n\t\t\tlabelPrefix: \"service\",\n\t\t\tconfidence: 0.7,\n\t\t},\n\t];\n\n\tconst candidate = candidates.find((item) => item.entityId);\n\tif (!candidate?.entityId) return null;\n\n\tconst route = evidenceRouteFor(\n\t\tcandidate.entityKind,\n\t\tcandidate.entityId,\n\t\ttraceId,\n\t);\n\treturn {\n\t\tentityKind: candidate.entityKind,\n\t\tentityId: candidate.entityId,\n\t\troute,\n\t\tlabel: `${candidate.labelPrefix} ${candidate.entityId}`,\n\t\tconfidence: candidate.confidence,\n\t\tcitations: traceCitation.filter(\n\t\t\t(citation) =>\n\t\t\t\tcitation.entityKind !== candidate.entityKind ||\n\t\t\t\tcitation.entityId !== candidate.entityId,\n\t\t),\n\t\tsuggestedNextPivots: [\n\t\t\t{\n\t\t\t\tlabel: `Open ${candidate.labelPrefix}`,\n\t\t\t\tentityKind: candidate.entityKind,\n\t\t\t\tentityId: candidate.entityId,\n\t\t\t\troute,\n\t\t\t\treason: \"Inspect the cited evidence row.\",\n\t\t\t},\n\t\t],\n\t};\n};\n\nexport const alertEvidenceReferences = (\n\trule: AlertRule,\n\tevaluation?: AlertEvaluation | null,\n): EvidenceReference[] => {\n\tconst route = evidenceRouteFor(\"alert\", rule.id);\n\tconst references: EvidenceReference[] = [\n\t\t{\n\t\t\tevidenceId: evaluation\n\t\t\t\t? `alert:${rule.id}:evaluation:${evaluation.id}`\n\t\t\t\t: `alert:${rule.id}:rule`,\n\t\t\tentityKind: \"alert\",\n\t\t\tentityId: rule.id,\n\t\t\troute,\n\t\t\tsource: evaluation\n\t\t\t\t? \"alert_evaluations\"\n\t\t\t\t: \"alerts.rule_evaluation_preview\",\n\t\t\tconfidence: evaluation ? 1 : 0.8,\n\t\t\treason: evaluation\n\t\t\t\t? `Alert \"${rule.name}\" evaluated to ${evaluation.value} and state ${evaluation.state}.`\n\t\t\t\t: `Alert \"${rule.name}\" was evaluated against its configured threshold.`,\n\t\t\tcitations: [\n\t\t\t\t{\n\t\t\t\t\tlabel: rule.name,\n\t\t\t\t\tentityKind: \"alert\",\n\t\t\t\t\tentityId: rule.id,\n\t\t\t\t\troute,\n\t\t\t\t},\n\t\t\t],\n\t\t\tsuggestedNextPivots: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Open alert\",\n\t\t\t\t\tentityKind: \"alert\",\n\t\t\t\t\tentityId: rule.id,\n\t\t\t\t\troute,\n\t\t\t\t\treason: \"Inspect the alert rule, threshold, and recent evaluations.\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t];\n\n\tif (rule.analysisId) {\n\t\tconst analysisRouteValue = analysisRoute(rule.analysisId);\n\t\treferences.push({\n\t\t\tevidenceId: evaluation\n\t\t\t\t? `alert:${rule.id}:evaluation:${evaluation.id}:analysis:${rule.analysisId}`\n\t\t\t\t: `alert:${rule.id}:analysis:${rule.analysisId}`,\n\t\t\tentityKind: \"analysis\",\n\t\t\tentityId: rule.analysisId,\n\t\t\troute: analysisRouteValue,\n\t\t\tsource: \"alert_rules.analysis_id\",\n\t\t\tconfidence: 0.9,\n\t\t\treason: `Alert \"${rule.name}\" is bound to analysis \"${rule.analysisId}\".`,\n\t\t\tcitations: [\n\t\t\t\t{\n\t\t\t\t\tlabel: rule.name,\n\t\t\t\t\tentityKind: \"alert\",\n\t\t\t\t\tentityId: rule.id,\n\t\t\t\t\troute,\n\t\t\t\t},\n\t\t\t],\n\t\t\tsuggestedNextPivots: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Open investigation\",\n\t\t\t\t\tentityKind: \"analysis\",\n\t\t\t\t\tentityId: rule.analysisId,\n\t\t\t\t\troute: analysisRouteValue,\n\t\t\t\t\treason: \"Review the analysis result that drives this alert.\",\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\treturn references;\n};\n\nexport const aiEvaluationEvidenceReferences = (\n\tevaluation: Pick<\n\t\tAIEvaluationRecord,\n\t\t\"evaluationId\" | \"traceId\" | \"spanId\" | \"name\" | \"source\"\n\t>,\n): EvidenceReference[] => {\n\tconst spanEntityId = `${evaluation.traceId}:${evaluation.spanId}`;\n\tconst spanRouteValue = evidenceRouteFor(\n\t\t\"span\",\n\t\tspanEntityId,\n\t\tevaluation.traceId,\n\t);\n\treturn [\n\t\t{\n\t\t\tevidenceId: `ai-evaluation:${evaluation.evaluationId}`,\n\t\t\tentityKind: \"eval\",\n\t\t\tentityId: evaluation.evaluationId,\n\t\t\troute: evidenceRouteFor(\"eval\", evaluation.evaluationId),\n\t\t\tsource: `ai_span_evaluations.${evaluation.source}`,\n\t\t\tconfidence: 0.95,\n\t\t\treason: `AI evaluation \"${evaluation.name}\" graded span \"${evaluation.spanId}\".`,\n\t\t\tcitations: [\n\t\t\t\t{\n\t\t\t\t\tlabel: `span ${evaluation.spanId}`,\n\t\t\t\t\tentityKind: \"span\",\n\t\t\t\t\tentityId: spanEntityId,\n\t\t\t\t\troute: spanRouteValue,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: `trace ${evaluation.traceId}`,\n\t\t\t\t\tentityKind: \"trace\",\n\t\t\t\t\tentityId: evaluation.traceId,\n\t\t\t\t\troute: evidenceRouteFor(\"trace\", evaluation.traceId),\n\t\t\t\t},\n\t\t\t],\n\t\t\tsuggestedNextPivots: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Open span\",\n\t\t\t\t\tentityKind: \"span\",\n\t\t\t\t\tentityId: spanEntityId,\n\t\t\t\t\troute: spanRouteValue,\n\t\t\t\t\treason: \"Inspect the evaluated AI span and its surrounding trace.\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t];\n};\n\nexport interface SourceEvidenceLinks {\n\tsourceAgentRunId?: string | null;\n\tsourceActionId?: string | null;\n\tsourceToolCallId?: string | null;\n\tsourceTraceId?: string | null;\n\tsourceSpanId?: string | null;\n}\n\nexport const sourceLinkEvidenceReferences = (\n\tsource: {\n\t\tsourceLabel: string;\n\t\tsourceId: string;\n\t\tsourceKind: \"eval_case\" | \"eval_case_result\" | \"eval_run\";\n\t\tsourceRoute?: string | null;\n\t\tsourceName?: string | null;\n\t},\n\tlinks: SourceEvidenceLinks,\n): EvidenceReference[] => {\n\tconst citations: EvidenceCitation[] = [];\n\tif (source.sourceRoute) {\n\t\tcitations.push({\n\t\t\tlabel: source.sourceName ?? source.sourceId,\n\t\t\tentityKind: \"eval\",\n\t\t\tentityId: source.sourceId,\n\t\t\troute: source.sourceRoute,\n\t\t});\n\t}\n\n\tconst candidates: Array<{\n\t\tkind: EvidenceEntityKind;\n\t\tid: string | null | undefined;\n\t\tlabel: string;\n\t\tconfidence: number;\n\t}> = [\n\t\t{\n\t\t\tkind: \"action\",\n\t\t\tid: links.sourceActionId,\n\t\t\tlabel: \"source action\",\n\t\t\tconfidence: 0.92,\n\t\t},\n\t\t{\n\t\t\tkind: \"tool_call\",\n\t\t\tid: links.sourceToolCallId,\n\t\t\tlabel: \"source tool call\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tkind: \"agent_run\",\n\t\t\tid: links.sourceAgentRunId,\n\t\t\tlabel: \"source agent run\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tkind: \"span\",\n\t\t\tid:\n\t\t\t\tlinks.sourceTraceId && links.sourceSpanId\n\t\t\t\t\t? `${links.sourceTraceId}:${links.sourceSpanId}`\n\t\t\t\t\t: null,\n\t\t\tlabel: \"source span\",\n\t\t\tconfidence: 0.9,\n\t\t},\n\t\t{\n\t\t\tkind: \"trace\",\n\t\t\tid: links.sourceTraceId,\n\t\t\tlabel: \"source trace\",\n\t\t\tconfidence: 0.88,\n\t\t},\n\t];\n\n\treturn candidates\n\t\t.filter((candidate): candidate is typeof candidate & { id: string } =>\n\t\t\tBoolean(candidate.id),\n\t\t)\n\t\t.map((candidate) => {\n\t\t\tconst route = evidenceRouteFor(\n\t\t\t\tcandidate.kind,\n\t\t\t\tcandidate.id,\n\t\t\t\tlinks.sourceTraceId,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tevidenceId: `${source.sourceKind}:${source.sourceId}:${candidate.kind}:${candidate.id}`,\n\t\t\t\tentityKind: candidate.kind,\n\t\t\t\tentityId: candidate.id,\n\t\t\t\troute,\n\t\t\t\tsource: `${source.sourceKind}.source_links`,\n\t\t\t\tconfidence: candidate.confidence,\n\t\t\t\treason: `${source.sourceLabel} links to ${candidate.label} \"${candidate.id}\".`,\n\t\t\t\tcitations,\n\t\t\t\tsuggestedNextPivots: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: `Open ${candidate.label}`,\n\t\t\t\t\t\tentityKind: candidate.kind,\n\t\t\t\t\t\tentityId: candidate.id,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\"Inspect the production evidence behind this evaluation item.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t} satisfies EvidenceReference;\n\t\t});\n};\n\nexport const analysisResultEvidenceReferences = (\n\tresult: Pick<AnalysisResult, \"analysisId\" | \"payload\">,\n): EvidenceReference[] => {\n\tconst references: EvidenceReference[] = [\n\t\t...instrumentationGapEvidenceReferences(result),\n\t];\n\tfor (const { sectionName, table } of parseEvidenceTables(result.payload)) {\n\t\tconst headers = table.headers.map(normalizeHeader);\n\t\tfor (const [rowIndex, row] of table.rows.entries()) {\n\t\t\tif (references.length >= MAX_ANALYSIS_EVIDENCE_REFERENCES) {\n\t\t\t\treturn references;\n\t\t\t}\n\t\t\tconst entity = entityFromRow(row, headers);\n\t\t\tif (!entity) continue;\n\n\t\t\tconst title = table.title ?? sectionName;\n\t\t\treferences.push({\n\t\t\t\tevidenceId: `analysis:${result.analysisId}:${sectionName}:${rowIndex}:${entity.entityKind}:${entity.entityId}`,\n\t\t\t\tentityKind: entity.entityKind,\n\t\t\t\tentityId: entity.entityId,\n\t\t\t\troute: entity.route,\n\t\t\t\tsource: `analysis.payload.evidence.${sectionName}`,\n\t\t\t\tconfidence: entity.confidence,\n\t\t\t\treason: `Analysis \"${result.analysisId}\" cited ${entity.label} in \"${title}\" row ${rowIndex + 1}.`,\n\t\t\t\tcitations: entity.citations,\n\t\t\t\tsuggestedNextPivots: entity.suggestedNextPivots,\n\t\t\t});\n\t\t}\n\t}\n\treturn references;\n};\n","/**\n * RFC 0002 Stage 3 — narrative LLM adapter.\n *\n * Single-provider for v1 (Anthropic). The shape is \"stuff in a system\n * prompt that bakes the rendering rules, ask for one declarative\n * sentence, return text\" — a different provider plugs in here without\n * changing callers.\n *\n * Rendering rules from the RFC's UX section, enforced at the system\n * prompt layer so the model can't accidentally drift:\n *\n * 1. Never start with \"I\" or \"Here's\" — declarative, not chatbot.\n * 2. Always cite — if the prompt mentions \"the trace\", a trace_id\n * must follow inline.\n * 3. Include a time anchor (\"starting 8 min ago\", \"since 14:32\").\n * 4. ≤2 sentences. We're labelling tiles, not writing essays.\n */\n\nimport type { AnalysisDefinition, AnalysisResult } from \"@obsunified/types\";\nimport type { ChildSpanRunner } from \"../framework/logger\";\n\nexport interface NarrativeRequest {\n\tdefinition: AnalysisDefinition;\n\tcurrent: AnalysisResult;\n\tprevious: AnalysisResult | null;\n}\n\n/**\n * Provider discriminator. The collector picks one based on which API key\n * env var is set (OpenAI takes precedence when both are set, since users\n * who configure both presumably did so deliberately for OpenAI).\n *\n * anthropic → POST https://api.anthropic.com/v1/messages\n * openai → POST https://api.openai.com/v1/chat/completions\n * (apiUrl override lets you point at openrouter, vLLM,\n * azure-openai-compat, ollama, etc.)\n */\nexport type LlmProvider = \"anthropic\" | \"openai\";\n\nexport interface LlmConfig {\n\tprovider: LlmProvider;\n\tapiKey: string;\n\t/** Model id. Provider-specific. e.g. \"claude-haiku-4-5\" / \"gpt-4o-mini\". */\n\tmodel: string;\n\t/** Override base URL — Anthropic-compatible or OpenAI-compatible. */\n\tapiUrl?: string;\n\t/** Optional abort signal used to cap provider calls from schedulers. */\n\tsignal?: AbortSignal;\n\t/**\n\t * Optional child-span runner. When set, every LLM API call (and tool\n\t * dispatch in the agent loop) is wrapped in an OpenInference-shaped span\n\t * with `gen_ai.system`, `gen_ai.request.model`, and post-call usage\n\t * attributes. Wired by the worker entrypoint via `runtime.withChildSpan`.\n\t */\n\ttracer?: ChildSpanRunner;\n}\n\nconst DEFAULT_API_URL = \"https://api.anthropic.com/v1/messages\";\n\nconst SYSTEM_PROMPT = `You write one-sentence operational narratives for a self-hosted observability dashboard.\n\nHard rules — violations are unusable:\n1. Never start with \"I\", \"Here's\", \"We\", \"It looks\", or any first-person/chatbot framing. Write declarative statements about the system.\n2. If you reference a trace, span, log, or service, include the supplied identifier inline (e.g. \"trace abc12345\").\n3. Include a time anchor: \"starting Nm ago\", \"for the last Nm\", \"since HH:MM\" — pull from the inputs.\n4. ≤ 2 sentences total, ≤ 220 characters. Numbers belong in the sentence, not in a separate label.\n5. If the inputs are insufficient to write a useful sentence, output exactly the string NO_NARRATIVE (no quotes).\n\nTone: like a senior on-call writing a Slack message. Specific, blunt, no hedging, no advice (\"you should...\"), no questions.`;\n\nconst renderTemplate = (\n\ttemplate: string,\n\tdef: AnalysisDefinition,\n\tr: AnalysisResult,\n): string => {\n\tconst scope = (def.scope ?? {}) as Record<string, unknown>;\n\tconst traceIds = Array.isArray(r.payload?.trace_ids)\n\t\t? (r.payload.trace_ids as unknown[])\n\t\t\t\t.filter((x): x is string => typeof x === \"string\")\n\t\t\t\t.slice(0, 5)\n\t\t\t\t.join(\", \")\n\t\t: \"\";\n\tconst subs: Record<string, string> = {\n\t\ttitle: def.title,\n\t\tstatus: r.status,\n\t\tprimary: r.primaryValue === null ? \"n/a\" : String(r.primaryValue),\n\t\tbaseline: r.baselineValue === null ? \"n/a\" : String(r.baselineValue),\n\t\tdelta_pct: r.deltaPct === null ? \"n/a\" : `${r.deltaPct.toFixed(1)}%`,\n\t\tservice: typeof scope.service === \"string\" ? scope.service : \"\",\n\t\ttrace_ids: traceIds,\n\t};\n\treturn template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, k) => subs[k] ?? \"\");\n};\n\nconst buildUserPrompt = ({\n\tdefinition,\n\tcurrent,\n\tprevious,\n}: NarrativeRequest): string => {\n\tconst lines: string[] = [];\n\tconst tmplRendered = renderTemplate(\n\t\tdefinition.narrate?.prompt ?? \"\",\n\t\tdefinition,\n\t\tcurrent,\n\t).trim();\n\tif (tmplRendered) lines.push(tmplRendered);\n\n\tlines.push(\"\");\n\tlines.push(\"Current state:\");\n\tlines.push(`- panel: ${definition.title}`);\n\tlines.push(`- status: ${current.status}`);\n\tif (current.primaryValue !== null)\n\t\tlines.push(`- primary: ${current.primaryValue}`);\n\tif (current.baselineValue !== null)\n\t\tlines.push(`- baseline (1h): ${current.baselineValue}`);\n\tif (current.deltaPct !== null)\n\t\tlines.push(`- delta vs baseline: ${current.deltaPct.toFixed(1)}%`);\n\n\tconst since = previous\n\t\t? Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.round(\n\t\t\t\t\t(new Date(current.generatedAt).getTime() -\n\t\t\t\t\t\tnew Date(previous.generatedAt).getTime()) /\n\t\t\t\t\t\t60000,\n\t\t\t\t),\n\t\t\t)\n\t\t: null;\n\tif (since !== null) {\n\t\tlines.push(\n\t\t\t`- previous run was ${since} minute${since === 1 ? \"\" : \"s\"} ago, status=${previous?.status ?? \"n/a\"}`,\n\t\t);\n\t}\n\n\tconst payloadKeys = Object.keys(current.payload ?? {}).filter(\n\t\t(k) => k !== \"sparkline\",\n\t);\n\tif (payloadKeys.length > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(\"Payload details:\");\n\t\tfor (const k of payloadKeys) {\n\t\t\tconst v = current.payload[k];\n\t\t\tconst rendered =\n\t\t\t\ttypeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\"\n\t\t\t\t\t? String(v)\n\t\t\t\t\t: JSON.stringify(v);\n\t\t\tif (rendered.length <= 200) lines.push(`- ${k}: ${rendered}`);\n\t\t}\n\t}\n\n\tlines.push(\"\");\n\tlines.push(\n\t\t\"Output: a single sentence (max two), no preamble, no quotes, no Markdown.\",\n\t);\n\treturn lines.join(\"\\n\");\n};\n\nexport class LlmCallError extends Error {\n\treadonly status: number | null;\n\tconstructor(message: string, status: number | null = null) {\n\t\tsuper(message);\n\t\tthis.name = \"LlmCallError\";\n\t\tthis.status = status;\n\t}\n}\n\n/**\n * Provider dispatcher for narrative generation.\n *\n * Returns `null` when:\n * - the model returns the sentinel NO_NARRATIVE\n * - the response is empty\n * Throws `LlmCallError` on non-2xx HTTP responses so the runner can\n * count the failure against the budget without writing a bogus narrative.\n */\nexport async function generateNarrative(\n\treq: NarrativeRequest,\n\tconfig: LlmConfig,\n): Promise<string | null> {\n\tif (config.provider === \"openai\") {\n\t\t// Lazy import to keep the Anthropic-only path zero-cost on the worker\n\t\t// boot and avoid a circular import between llm.ts and openai.ts.\n\t\tconst { generateNarrativeOpenAI } = await import(\"./openai\");\n\t\treturn generateNarrativeOpenAI(req, config);\n\t}\n\treturn generateNarrativeAnthropic(req, config);\n}\n\nasync function generateNarrativeAnthropic(\n\treq: NarrativeRequest,\n\tconfig: LlmConfig,\n): Promise<string | null> {\n\tconst url = config.apiUrl ?? DEFAULT_API_URL;\n\tconst userPrompt = buildUserPrompt(req);\n\n\tconst tracer = config.tracer;\n\tconst callLLM = async (\n\t\tspan: { setAttribute(k: string, v: unknown): void } | null,\n\t): Promise<string | null> => {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"x-api-key\": config.apiKey,\n\t\t\t\t\"anthropic-version\": \"2023-06-01\",\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tmodel: config.model,\n\t\t\t\tmax_tokens: 200,\n\t\t\t\tsystem: SYSTEM_PROMPT,\n\t\t\t\tmessages: [{ role: \"user\", content: userPrompt }],\n\t\t\t}),\n\t\t\tsignal: config.signal,\n\t\t});\n\t\tif (span) {\n\t\t\tspan.setAttribute(\"http.response.status_code\", response.status);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new LlmCallError(\n\t\t\t\t`anthropic ${response.status}: ${text.slice(0, 200)}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst body = (await response.json()) as {\n\t\t\tcontent?: Array<{ type?: string; text?: string }>;\n\t\t\tusage?: { input_tokens?: number; output_tokens?: number };\n\t\t\tstop_reason?: string;\n\t\t};\n\t\tif (body.stop_reason === \"max_tokens\") {\n\t\t\tthrow new LlmCallError(\"anthropic response truncated by max_tokens\");\n\t\t}\n\t\tif (span && body.usage) {\n\t\t\tif (typeof body.usage.input_tokens === \"number\")\n\t\t\t\tspan.setAttribute(\"gen_ai.usage.input_tokens\", body.usage.input_tokens);\n\t\t\tif (typeof body.usage.output_tokens === \"number\")\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\"gen_ai.usage.output_tokens\",\n\t\t\t\t\tbody.usage.output_tokens,\n\t\t\t\t);\n\t\t}\n\t\tconst text = (body.content ?? [])\n\t\t\t.filter((block) => block?.type === \"text\")\n\t\t\t.map((block) => block?.text ?? \"\")\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (!text || text === \"NO_NARRATIVE\") return null;\n\t\t// Strip any accidental wrapping quotes the model might add despite the\n\t\t// system prompt.\n\t\treturn text.replace(/^[\"']|[\"']$/g, \"\").trim();\n\t};\n\n\tif (!tracer) return callLLM(null);\n\treturn tracer(\"llm.anthropic.messages\", async (span) => callLLM(span), {\n\t\t\"openinference.span.kind\": \"LLM\",\n\t\t\"gen_ai.system\": \"anthropic\",\n\t\t\"gen_ai.request.model\": config.model,\n\t\t\"gen_ai.request.max_tokens\": 200,\n\t});\n}\n"],"mappings":"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","names":["text"]}