@obsunified/collector 2.2.3 → 2.3.0
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/auth.d.ts +1 -1
- package/dist/{chunk-ZSF5XP3B.js → chunk-RASSTGNB.js} +30 -22
- package/dist/chunk-RASSTGNB.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2562 -2311
- package/dist/index.js.map +1 -1
- package/dist/{ingest-auth-C5-8Mly5.d.ts → ingest-auth-BmP5-f96.d.ts} +32 -1
- package/dist/{openai-GSJZGFGL.js → openai-VDKVVZAV.js} +2 -2
- package/package.json +4 -2
- package/src/__tests__/autosre-closed-loop.test.ts +110 -0
- package/src/__tests__/db-parity.integration.test.ts +961 -0
- package/src/__tests__/internal-contracts-routes.test.ts +451 -0
- package/src/framework/env.ts +105 -0
- package/src/index.ts +13 -4
- package/src/lib/ai-store/evaluations.test.ts +3 -3
- package/src/lib/analyses-store.test.ts +6 -6
- package/src/lib/db-parity.ts +883 -0
- package/src/lib/eval-cases-store.test.ts +1 -1
- package/src/lib/eval-cases-store.ts +16 -4
- package/src/lib/evidence-references.ts +30 -22
- package/src/lib/stage6.test.ts +1 -1
- package/src/migrations/parity.test.ts +105 -3
- package/src/plugins/ai-receiver.ts +6 -1
- package/src/plugins/autosre-routes.ts +251 -140
- package/src/plugins/connected-routes/manifest.test.ts +114 -32
- package/src/plugins/connected-routes/manifest.ts +59 -76
- package/src/plugins/connected-routes.test.ts +10 -8
- package/src/plugins/connected-routes.ts +42 -12
- package/src/plugins/eval-cases-routes.ts +33 -10
- package/src/plugins/evidence-retrieval-routes.ts +60 -25
- package/dist/chunk-ZSF5XP3B.js.map +0 -1
- /package/dist/{openai-GSJZGFGL.js.map → openai-VDKVVZAV.js.map} +0 -0
|
@@ -1,40 +1,25 @@
|
|
|
1
|
+
import { KNOWN_TABS, parseDashboardRouteHash } from "@obsunified/types";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
import {
|
|
3
4
|
hrefForEntity,
|
|
5
|
+
linksFromActions,
|
|
6
|
+
linksFromAi,
|
|
4
7
|
linksFromArtifacts,
|
|
5
8
|
linksFromEvalResults,
|
|
9
|
+
linksFromLogs,
|
|
10
|
+
linksFromMetricExemplars,
|
|
6
11
|
linksFromRetrievalEvents,
|
|
12
|
+
linksFromSpans,
|
|
7
13
|
linksFromToolCalls,
|
|
14
|
+
linksFromUsage,
|
|
15
|
+
linkToAction,
|
|
16
|
+
linkToAgentRun,
|
|
17
|
+
linkToSession,
|
|
18
|
+
linkToTrace,
|
|
8
19
|
MAX_LINKS_INLINE,
|
|
9
20
|
resolveFaultLocusTarget,
|
|
10
21
|
} from "./manifest";
|
|
11
22
|
|
|
12
|
-
// Tabs served by apps/web/src/app/router.ts (KNOWN_TABS). Generated hrefs
|
|
13
|
-
// must start with one of these or the app falls back to Health.
|
|
14
|
-
const ROUTER_TABS = new Set([
|
|
15
|
-
"health",
|
|
16
|
-
"investigate",
|
|
17
|
-
"traces",
|
|
18
|
-
"service-map",
|
|
19
|
-
"issues",
|
|
20
|
-
"logs",
|
|
21
|
-
"ai",
|
|
22
|
-
"evidence",
|
|
23
|
-
"usage",
|
|
24
|
-
"replay",
|
|
25
|
-
"alerts",
|
|
26
|
-
"users",
|
|
27
|
-
"agent-runs",
|
|
28
|
-
"actions",
|
|
29
|
-
"tool-calls",
|
|
30
|
-
"profiles",
|
|
31
|
-
"tool-reliability",
|
|
32
|
-
"evaluations",
|
|
33
|
-
"changes",
|
|
34
|
-
"findings",
|
|
35
|
-
"incidents",
|
|
36
|
-
"remediations",
|
|
37
|
-
]);
|
|
38
23
|
const tabOf = (href: string) =>
|
|
39
24
|
href.replace(/^#\//, "").split(/[/?#]/)[0] ?? "";
|
|
40
25
|
|
|
@@ -80,8 +65,105 @@ describe("connected manifest hrefs target real router tabs", () => {
|
|
|
80
65
|
"#/evaluations/e1",
|
|
81
66
|
"#/actions/a1",
|
|
82
67
|
]);
|
|
83
|
-
for (const href of hrefs)
|
|
84
|
-
expect(
|
|
68
|
+
for (const href of hrefs) {
|
|
69
|
+
expect(KNOWN_TABS.has(tabOf(href ?? ""))).toBe(true);
|
|
70
|
+
expect(parseDashboardRouteHash(href ?? "").ok).toBe(true);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("emits canonical routes from all link builders and round-trips through parseDashboardRouteHash", () => {
|
|
75
|
+
expect(linkToTrace("trace-1").href).toBe("#/traces?trace=trace-1");
|
|
76
|
+
expect(linkToSession("sess/1").href).toBe("#/replay?session=sess%2F1");
|
|
77
|
+
expect(linkToAction("act/1").href).toBe("#/actions/act%2F1");
|
|
78
|
+
expect(linkToAgentRun("run/1").href).toBe("#/agent-runs/run%2F1");
|
|
79
|
+
|
|
80
|
+
const inlineSpans = linksFromSpans(
|
|
81
|
+
[
|
|
82
|
+
{
|
|
83
|
+
traceId: "tr-1",
|
|
84
|
+
spanId: "sp-1",
|
|
85
|
+
serviceName: "api",
|
|
86
|
+
spanName: "GET /",
|
|
87
|
+
} as never,
|
|
88
|
+
],
|
|
89
|
+
"Spans",
|
|
90
|
+
);
|
|
91
|
+
expect(inlineSpans.links[0]?.href).toBe("#/traces?trace=tr-1&span=sp-1");
|
|
92
|
+
expect(parseDashboardRouteHash(inlineSpans.links[0]?.href ?? "")).toEqual({
|
|
93
|
+
ok: true,
|
|
94
|
+
route: { tab: "traces", traceId: "tr-1", spanId: "sp-1" },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const manySpans = linksFromSpans(
|
|
98
|
+
Array.from({ length: MAX_LINKS_INLINE + 1 }, (_, i) => ({
|
|
99
|
+
traceId: "tr-many",
|
|
100
|
+
spanId: `sp-${i}`,
|
|
101
|
+
serviceName: "api",
|
|
102
|
+
spanName: "op",
|
|
103
|
+
})) as never,
|
|
104
|
+
"Spans",
|
|
105
|
+
);
|
|
106
|
+
expect(manySpans.links[0]?.href).toBe("#/traces?q=tr-many");
|
|
107
|
+
|
|
108
|
+
const inlineLogs = linksFromLogs(
|
|
109
|
+
[
|
|
110
|
+
{
|
|
111
|
+
logId: "log-1",
|
|
112
|
+
traceId: "tr-1",
|
|
113
|
+
severity: "ERROR",
|
|
114
|
+
message: "boom",
|
|
115
|
+
} as never,
|
|
116
|
+
],
|
|
117
|
+
"Logs",
|
|
118
|
+
);
|
|
119
|
+
expect(inlineLogs.links[0]?.href).toBe("#/logs?id=log-1");
|
|
120
|
+
|
|
121
|
+
const inlineUsage = linksFromUsage(
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
eventId: "ev-1",
|
|
125
|
+
eventType: "click",
|
|
126
|
+
eventName: "buy",
|
|
127
|
+
} as never,
|
|
128
|
+
],
|
|
129
|
+
"Usage",
|
|
130
|
+
);
|
|
131
|
+
expect(inlineUsage.links[0]?.href).toBe("#/usage?id=ev-1");
|
|
132
|
+
|
|
133
|
+
const inlineExemplars = linksFromMetricExemplars(
|
|
134
|
+
[
|
|
135
|
+
{
|
|
136
|
+
traceId: "tr-1",
|
|
137
|
+
spanId: "sp-2",
|
|
138
|
+
serviceName: "api",
|
|
139
|
+
metricName: "latency",
|
|
140
|
+
value: 42,
|
|
141
|
+
tsNs: "100",
|
|
142
|
+
} as never,
|
|
143
|
+
],
|
|
144
|
+
"Exemplars",
|
|
145
|
+
);
|
|
146
|
+
expect(inlineExemplars.links[0]?.href).toBe(
|
|
147
|
+
"#/traces?trace=tr-1&span=sp-2",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const inlineAi = linksFromAi(
|
|
151
|
+
[
|
|
152
|
+
{
|
|
153
|
+
callId: "call/1",
|
|
154
|
+
provider: "openai",
|
|
155
|
+
modelName: "gpt-4o",
|
|
156
|
+
} as never,
|
|
157
|
+
],
|
|
158
|
+
"AI",
|
|
159
|
+
);
|
|
160
|
+
expect(inlineAi.links[0]?.href).toBe("#/ai/calls/call%2F1");
|
|
161
|
+
|
|
162
|
+
const inlineActions = linksFromActions(
|
|
163
|
+
[{ id: "act-1", actionKind: "step", name: "plan" } as never],
|
|
164
|
+
"Actions",
|
|
165
|
+
);
|
|
166
|
+
expect(inlineActions.links[0]?.href).toBe("#/actions/act-1");
|
|
85
167
|
});
|
|
86
168
|
|
|
87
169
|
it("hrefForEntity only emits router tabs for every evidence kind", () => {
|
|
@@ -104,12 +186,12 @@ describe("connected manifest hrefs target real router tabs", () => {
|
|
|
104
186
|
"replay",
|
|
105
187
|
"docs",
|
|
106
188
|
]) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
189
|
+
const href = hrefForEntity(kind, "id-1");
|
|
190
|
+
expect(KNOWN_TABS.has(tabOf(href)), kind).toBe(true);
|
|
191
|
+
expect(parseDashboardRouteHash(href).ok, kind).toBe(true);
|
|
110
192
|
}
|
|
111
193
|
expect(hrefForEntity("trace", "abc")).toBe("#/traces?trace=abc");
|
|
112
|
-
expect(hrefForEntity("alert", "al 1")).toBe("#/alerts?id=al
|
|
194
|
+
expect(hrefForEntity("alert", "al 1")).toBe("#/alerts?id=al+1");
|
|
113
195
|
expect(hrefForEntity("ai_call", "c1")).toBe("#/ai/calls/c1");
|
|
114
196
|
});
|
|
115
197
|
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
CausalProofTier,
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
type CausalProofTier,
|
|
3
|
+
dashboardRouteToHref,
|
|
4
|
+
type EvidenceEntityKind,
|
|
5
|
+
type RelationshipCertainty,
|
|
6
|
+
routeForEntity,
|
|
7
|
+
hrefForEntity as sharedHrefForEntity,
|
|
5
8
|
} from "@obsunified/types";
|
|
6
9
|
import type {
|
|
7
10
|
ActionRef,
|
|
@@ -19,6 +22,8 @@ import type {
|
|
|
19
22
|
import type { CausalConfidence } from "../../lib/identity-index/types";
|
|
20
23
|
import type { SqlDb } from "../../lib/sql-db";
|
|
21
24
|
|
|
25
|
+
export { routeForEntity };
|
|
26
|
+
|
|
22
27
|
export type ConnectedEntityKind =
|
|
23
28
|
| "span"
|
|
24
29
|
| "profile"
|
|
@@ -94,7 +99,7 @@ export const linkToTrace = (
|
|
|
94
99
|
label?: string,
|
|
95
100
|
): ConnectedLink => ({
|
|
96
101
|
label: label ?? `trace ${traceId.slice(0, 12)}`,
|
|
97
|
-
href:
|
|
102
|
+
href: dashboardRouteToHref({ tab: "traces", traceId }),
|
|
98
103
|
entityKind: "trace",
|
|
99
104
|
entityId: traceId,
|
|
100
105
|
});
|
|
@@ -108,7 +113,7 @@ export const linkToSession = (sessionId: string): ConnectedLink => ({
|
|
|
108
113
|
sessionId.length > 12
|
|
109
114
|
? `session …${sessionId.slice(-12)}`
|
|
110
115
|
: `session ${sessionId}`,
|
|
111
|
-
href:
|
|
116
|
+
href: dashboardRouteToHref({ tab: "replay", sessionId }),
|
|
112
117
|
sample: sessionId,
|
|
113
118
|
entityKind: "replay",
|
|
114
119
|
entityId: sessionId,
|
|
@@ -131,7 +136,10 @@ export const linksFromSpans = (
|
|
|
131
136
|
links: [
|
|
132
137
|
{
|
|
133
138
|
label: `${spans.length} spans`,
|
|
134
|
-
href:
|
|
139
|
+
href: dashboardRouteToHref({
|
|
140
|
+
tab: "traces",
|
|
141
|
+
query: spans[0].traceId,
|
|
142
|
+
}),
|
|
135
143
|
count: spans.length,
|
|
136
144
|
sample: truncate(
|
|
137
145
|
`${spans[0].serviceName ?? "?"} · ${spans[0].spanName}`,
|
|
@@ -144,7 +152,11 @@ export const linksFromSpans = (
|
|
|
144
152
|
label: prefix,
|
|
145
153
|
links: spans.map((s) => ({
|
|
146
154
|
label: `${s.serviceName ?? "?"} · ${truncate(s.spanName, 40)}`,
|
|
147
|
-
href:
|
|
155
|
+
href: dashboardRouteToHref({
|
|
156
|
+
tab: "traces",
|
|
157
|
+
traceId: s.traceId,
|
|
158
|
+
spanId: s.spanId,
|
|
159
|
+
}),
|
|
148
160
|
})),
|
|
149
161
|
};
|
|
150
162
|
};
|
|
@@ -167,7 +179,10 @@ export const linksFromLogs = (
|
|
|
167
179
|
links: [
|
|
168
180
|
{
|
|
169
181
|
label: `${logs.length} logs`,
|
|
170
|
-
href:
|
|
182
|
+
href: dashboardRouteToHref({
|
|
183
|
+
tab: "logs",
|
|
184
|
+
...(sample.traceId ? { query: sample.traceId } : {}),
|
|
185
|
+
}),
|
|
171
186
|
count: logs.length,
|
|
172
187
|
sample: truncate(sample.message),
|
|
173
188
|
},
|
|
@@ -178,7 +193,7 @@ export const linksFromLogs = (
|
|
|
178
193
|
label: prefix,
|
|
179
194
|
links: logs.map((l) => ({
|
|
180
195
|
label: `[${l.severity}] ${truncate(l.message, 60)}`,
|
|
181
|
-
href:
|
|
196
|
+
href: dashboardRouteToHref({ tab: "logs", logId: l.logId }),
|
|
182
197
|
})),
|
|
183
198
|
};
|
|
184
199
|
};
|
|
@@ -200,7 +215,7 @@ export const linksFromUsage = (
|
|
|
200
215
|
links: [
|
|
201
216
|
{
|
|
202
217
|
label: `${events.length} usage events`,
|
|
203
|
-
href:
|
|
218
|
+
href: dashboardRouteToHref({ tab: "usage" }),
|
|
204
219
|
count: events.length,
|
|
205
220
|
sample: truncate(`${events[0].eventType} · ${events[0].eventName}`),
|
|
206
221
|
},
|
|
@@ -211,7 +226,7 @@ export const linksFromUsage = (
|
|
|
211
226
|
label: prefix,
|
|
212
227
|
links: events.map((e) => ({
|
|
213
228
|
label: `${e.eventType} · ${truncate(e.eventName, 50)}`,
|
|
214
|
-
href:
|
|
229
|
+
href: dashboardRouteToHref({ tab: "usage", usageId: e.eventId }),
|
|
215
230
|
})),
|
|
216
231
|
};
|
|
217
232
|
};
|
|
@@ -235,8 +250,8 @@ export const linksFromMetricExemplars = (
|
|
|
235
250
|
{
|
|
236
251
|
label: `${exemplars.length} metric exemplars`,
|
|
237
252
|
href: sample.traceId
|
|
238
|
-
?
|
|
239
|
-
:
|
|
253
|
+
? dashboardRouteToHref({ tab: "traces", traceId: sample.traceId })
|
|
254
|
+
: dashboardRouteToHref({ tab: "traces", query: sample.metricName }),
|
|
240
255
|
count: exemplars.length,
|
|
241
256
|
sample: truncate(
|
|
242
257
|
`${sample.serviceName ?? "?"} · ${sample.metricName}=${sample.value}`,
|
|
@@ -250,10 +265,12 @@ export const linksFromMetricExemplars = (
|
|
|
250
265
|
links: exemplars.map((e) => ({
|
|
251
266
|
label: `${e.serviceName ?? "?"} · ${truncate(e.metricName, 40)}=${e.value}`,
|
|
252
267
|
href: e.traceId
|
|
253
|
-
?
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
? dashboardRouteToHref({
|
|
269
|
+
tab: "traces",
|
|
270
|
+
traceId: e.traceId,
|
|
271
|
+
...(e.spanId ? { spanId: e.spanId } : {}),
|
|
272
|
+
})
|
|
273
|
+
: dashboardRouteToHref({ tab: "traces", query: e.metricName }),
|
|
257
274
|
sample: e.tsNs,
|
|
258
275
|
})),
|
|
259
276
|
};
|
|
@@ -313,7 +330,11 @@ export const profileLinksForTrace = async (
|
|
|
313
330
|
label: `${icon} ${type === "offcpu" ? "Off-CPU profiles" : `${type[0].toUpperCase()}${type.slice(1)} profiles`}`,
|
|
314
331
|
links: list.map((r) => ({
|
|
315
332
|
label: `${r.service_name ?? "?"} · ${r.duration_ms}ms`,
|
|
316
|
-
href:
|
|
333
|
+
href: dashboardRouteToHref({
|
|
334
|
+
tab: "profiles",
|
|
335
|
+
profileId: r.id,
|
|
336
|
+
traceId,
|
|
337
|
+
}),
|
|
317
338
|
})),
|
|
318
339
|
});
|
|
319
340
|
}
|
|
@@ -337,7 +358,7 @@ export const linksFromAi = (
|
|
|
337
358
|
links: [
|
|
338
359
|
{
|
|
339
360
|
label: `${calls.length} AI calls`,
|
|
340
|
-
href:
|
|
361
|
+
href: dashboardRouteToHref({ tab: "ai" }),
|
|
341
362
|
count: calls.length,
|
|
342
363
|
sample: truncate(`${calls[0].provider} · ${calls[0].modelName}`),
|
|
343
364
|
},
|
|
@@ -348,7 +369,7 @@ export const linksFromAi = (
|
|
|
348
369
|
label: prefix,
|
|
349
370
|
links: calls.map((c) => ({
|
|
350
371
|
label: `${c.provider} · ${c.modelName}`,
|
|
351
|
-
href:
|
|
372
|
+
href: dashboardRouteToHref({ tab: "ai", aiCallId: c.callId }),
|
|
352
373
|
entityKind: "ai_call",
|
|
353
374
|
entityId: c.callId,
|
|
354
375
|
})),
|
|
@@ -360,7 +381,7 @@ export const linkToAction = (
|
|
|
360
381
|
label?: string,
|
|
361
382
|
): ConnectedLink => ({
|
|
362
383
|
label: label ?? `action ${actionId.slice(0, 12)}`,
|
|
363
|
-
href:
|
|
384
|
+
href: dashboardRouteToHref({ tab: "actions", actionId }),
|
|
364
385
|
entityKind: "action",
|
|
365
386
|
entityId: actionId,
|
|
366
387
|
});
|
|
@@ -370,7 +391,7 @@ export const linkToAgentRun = (
|
|
|
370
391
|
label?: string,
|
|
371
392
|
): ConnectedLink => ({
|
|
372
393
|
label: label ?? `agent run ${runId.slice(0, 12)}`,
|
|
373
|
-
href:
|
|
394
|
+
href: dashboardRouteToHref({ tab: "agent-runs", agentRunId: runId }),
|
|
374
395
|
entityKind: "agent_run",
|
|
375
396
|
entityId: runId,
|
|
376
397
|
});
|
|
@@ -413,7 +434,7 @@ export const linksFromActions = (
|
|
|
413
434
|
links: [
|
|
414
435
|
{
|
|
415
436
|
label: `${actions.length} actions`,
|
|
416
|
-
href:
|
|
437
|
+
href: dashboardRouteToHref({ tab: "actions" }),
|
|
417
438
|
count: actions.length,
|
|
418
439
|
sample: truncate(
|
|
419
440
|
`${actions[0].actionKind} · ${actions[0].name ?? "unnamed"}`,
|
|
@@ -429,7 +450,7 @@ export const linksFromActions = (
|
|
|
429
450
|
label: prefix,
|
|
430
451
|
links: actions.map((a) => ({
|
|
431
452
|
label: `[${a.actionKind}] ${truncate(a.name ?? "unnamed", 40)}`,
|
|
432
|
-
href:
|
|
453
|
+
href: dashboardRouteToHref({ tab: "actions", actionId: a.id }),
|
|
433
454
|
entityKind: "action",
|
|
434
455
|
entityId: a.id,
|
|
435
456
|
...actionContextMetadata(a, metadata),
|
|
@@ -457,7 +478,7 @@ export const linksFromToolCalls = (
|
|
|
457
478
|
links: [
|
|
458
479
|
{
|
|
459
480
|
label: `${calls.length} tool calls`,
|
|
460
|
-
href:
|
|
481
|
+
href: dashboardRouteToHref({ tab: "tool-calls" }),
|
|
461
482
|
count: calls.length,
|
|
462
483
|
sample: truncate(calls[0].toolName),
|
|
463
484
|
entityKind: "tool_call",
|
|
@@ -471,7 +492,7 @@ export const linksFromToolCalls = (
|
|
|
471
492
|
label: prefix,
|
|
472
493
|
links: calls.map((t) => ({
|
|
473
494
|
label: `tool: ${t.toolName}`,
|
|
474
|
-
href:
|
|
495
|
+
href: dashboardRouteToHref({ tab: "tool-calls", toolCallId: t.id }),
|
|
475
496
|
entityKind: "tool_call",
|
|
476
497
|
entityId: t.id,
|
|
477
498
|
...metadataForAction?.(t.actionId),
|
|
@@ -495,7 +516,7 @@ export const linksFromRetrievalEvents = (
|
|
|
495
516
|
links: events.map((r) => ({
|
|
496
517
|
label: `retrieve: ${r.retrieverName} (${r.totalResults} docs)`,
|
|
497
518
|
// No dedicated retrieval tab: the owning action's detail page lists it.
|
|
498
|
-
href:
|
|
519
|
+
href: dashboardRouteToHref({ tab: "actions", actionId: r.actionId }),
|
|
499
520
|
})),
|
|
500
521
|
};
|
|
501
522
|
};
|
|
@@ -520,7 +541,7 @@ export const linksFromEvalResults = (
|
|
|
520
541
|
label: `eval: ${e.evaluatorName} (${e.passed ? "passed" : "failed"})`,
|
|
521
542
|
// The Evaluations tab resolves this id to an eval case, falling back
|
|
522
543
|
// to the case saved from the same action.
|
|
523
|
-
href:
|
|
544
|
+
href: dashboardRouteToHref({ tab: "evaluations", evaluationId: e.id }),
|
|
524
545
|
entityKind: "eval",
|
|
525
546
|
entityId: e.id,
|
|
526
547
|
...metadataForAction?.(e.actionId),
|
|
@@ -544,59 +565,21 @@ export const linksFromArtifacts = (
|
|
|
544
565
|
links: arts.map((a) => ({
|
|
545
566
|
label: `artifact: ${a.artifactName} (${a.artifactType})`,
|
|
546
567
|
// No dedicated artifact tab: the owning action's detail page lists it.
|
|
547
|
-
href:
|
|
568
|
+
href: dashboardRouteToHref({ tab: "actions", actionId: a.actionId }),
|
|
548
569
|
})),
|
|
549
570
|
};
|
|
550
571
|
};
|
|
551
572
|
|
|
552
573
|
/**
|
|
553
|
-
* Dashboard destination for an evidence entity,
|
|
554
|
-
*
|
|
555
|
-
*
|
|
574
|
+
* Dashboard destination for an evidence entity, delegating to the shared
|
|
575
|
+
* `@obsunified/types` route contract. Unknown kinds fall back to the
|
|
576
|
+
* evidence explorer so a link never lands on the Health tab.
|
|
556
577
|
*/
|
|
557
|
-
export const hrefForEntity = (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
case "span": {
|
|
563
|
-
const [traceId, spanId] = id.split(":");
|
|
564
|
-
return spanId && traceId
|
|
565
|
-
? `#/traces?trace=${enc(traceId)}&span=${enc(spanId)}`
|
|
566
|
-
: `#/evidence?kind=span&id=${enc(id)}`;
|
|
567
|
-
}
|
|
568
|
-
case "alert":
|
|
569
|
-
return `#/alerts?id=${enc(id)}`;
|
|
570
|
-
case "analysis":
|
|
571
|
-
return "#/health";
|
|
572
|
-
case "service":
|
|
573
|
-
return `#/service-map?service=${enc(id)}`;
|
|
574
|
-
case "agent_run":
|
|
575
|
-
return `#/agent-runs/${enc(id)}`;
|
|
576
|
-
case "action":
|
|
577
|
-
return `#/actions/${enc(id)}`;
|
|
578
|
-
case "tool_call":
|
|
579
|
-
return `#/tool-calls/${enc(id)}`;
|
|
580
|
-
case "ai_call":
|
|
581
|
-
return `#/ai/calls/${enc(id)}`;
|
|
582
|
-
case "profile":
|
|
583
|
-
return `#/profiles/${enc(id)}`;
|
|
584
|
-
case "change":
|
|
585
|
-
return `#/changes/${enc(id)}`;
|
|
586
|
-
case "finding":
|
|
587
|
-
return `#/findings/${enc(id)}`;
|
|
588
|
-
case "incident":
|
|
589
|
-
return `#/incidents/${enc(id)}`;
|
|
590
|
-
case "eval":
|
|
591
|
-
return `#/evaluations/${enc(id)}`;
|
|
592
|
-
case "log":
|
|
593
|
-
return `#/logs?id=${enc(id)}`;
|
|
594
|
-
case "replay":
|
|
595
|
-
return `#/replay?session=${enc(id)}`;
|
|
596
|
-
default:
|
|
597
|
-
return `#/evidence?kind=${enc(kind)}&id=${enc(id)}`;
|
|
598
|
-
}
|
|
599
|
-
};
|
|
578
|
+
export const hrefForEntity = (
|
|
579
|
+
kind: string,
|
|
580
|
+
id: string,
|
|
581
|
+
options?: { traceId?: string; spanId?: string },
|
|
582
|
+
): string => sharedHrefForEntity(kind, id, options);
|
|
600
583
|
|
|
601
584
|
export interface FaultLocusTarget {
|
|
602
585
|
entityKind: "service" | "span";
|
|
@@ -335,7 +335,7 @@ describe("ConnectedRail manifest — span profile section (RFC 0009 #5)", () =>
|
|
|
335
335
|
expect(exemplarSection).toBeDefined();
|
|
336
336
|
expect(exemplarSection?.links[0].label).toContain("http.server.duration");
|
|
337
337
|
expect(exemplarSection?.links[0].href).toBe(
|
|
338
|
-
"#/traces
|
|
338
|
+
"#/traces?trace=trace-metric&span=span-hot",
|
|
339
339
|
);
|
|
340
340
|
});
|
|
341
341
|
});
|
|
@@ -673,7 +673,7 @@ describe("ConnectedRail manifest — raw signal action back-links", () => {
|
|
|
673
673
|
?.links[0],
|
|
674
674
|
).toMatchObject({
|
|
675
675
|
label: "[agent.step] Fallback normalized action",
|
|
676
|
-
href: "#/actions/fallback
|
|
676
|
+
href: "#/actions/fallback%3Atrace-fallback%3Aagent.step%3A1",
|
|
677
677
|
causalConfidence: ActionConfidence.Fallback,
|
|
678
678
|
source: "trace_id",
|
|
679
679
|
confidence: 0.55,
|
|
@@ -831,12 +831,12 @@ describe("ConnectedRail manifest — profile source entity", () => {
|
|
|
831
831
|
const traceSection = m.across.find((s) => s.label === "Sampled traces");
|
|
832
832
|
expect(traceSection?.links[0]).toMatchObject({
|
|
833
833
|
label: "trace trace-prof",
|
|
834
|
-
href: "#/traces
|
|
834
|
+
href: "#/traces?trace=trace-prof",
|
|
835
835
|
});
|
|
836
836
|
|
|
837
837
|
const spanSection = m.across.find((s) => s.label === "Sampled spans");
|
|
838
838
|
expect(spanSection?.links[0].href).toBe(
|
|
839
|
-
"#/traces
|
|
839
|
+
"#/traces?trace=trace-prof&span=span-hot",
|
|
840
840
|
);
|
|
841
841
|
|
|
842
842
|
const actionSection = m.down.find((s) =>
|
|
@@ -1108,7 +1108,9 @@ describe("ConnectedRail scenario acceptance contracts", () => {
|
|
|
1108
1108
|
const sessionSpans = sessionManifest.across.find((s) =>
|
|
1109
1109
|
s.label.toLowerCase().includes("span"),
|
|
1110
1110
|
);
|
|
1111
|
-
expect(sessionSpans?.links[0].href).toBe(
|
|
1111
|
+
expect(sessionSpans?.links[0].href).toBe(
|
|
1112
|
+
"#/traces?trace=trace-a&span=span-hot",
|
|
1113
|
+
);
|
|
1112
1114
|
|
|
1113
1115
|
const spanManifest = await fetch(
|
|
1114
1116
|
"/internal/connected/span/trace-a:span-hot",
|
|
@@ -1289,7 +1291,7 @@ describe("ConnectedRail scenario acceptance contracts", () => {
|
|
|
1289
1291
|
s.label.toLowerCase().includes("span"),
|
|
1290
1292
|
);
|
|
1291
1293
|
expect(sessionSpans?.links[0].href).toBe(
|
|
1292
|
-
"#/traces
|
|
1294
|
+
"#/traces?trace=trace-ai-heavy&span=span-llm",
|
|
1293
1295
|
);
|
|
1294
1296
|
|
|
1295
1297
|
const spanManifest = await fetch(
|
|
@@ -1464,7 +1466,7 @@ describe("ConnectedRail manifest — agent action graph entities (RFC 0010)", ()
|
|
|
1464
1466
|
// OTel Trace in related
|
|
1465
1467
|
const relatedTrace = m.related.find((s) => s.label === "OTel Trace");
|
|
1466
1468
|
expect(relatedTrace).toBeDefined();
|
|
1467
|
-
expect(relatedTrace?.links[0].href).toBe("#/traces
|
|
1469
|
+
expect(relatedTrace?.links[0].href).toBe("#/traces?trace=tx-789");
|
|
1468
1470
|
});
|
|
1469
1471
|
|
|
1470
1472
|
it("action detail raw manifest exposes causal confidence on action refs", async () => {
|
|
@@ -1650,7 +1652,7 @@ describe("ConnectedRail manifest — agent action graph entities (RFC 0010)", ()
|
|
|
1650
1652
|
// Up has Agent
|
|
1651
1653
|
const agentSec = m.up.find((s) => s.label === "Agent");
|
|
1652
1654
|
expect(agentSec).toBeDefined();
|
|
1653
|
-
expect(agentSec?.links[0].href).toBe("#/
|
|
1655
|
+
expect(agentSec?.links[0].href).toBe("#/evidence?kind=agent&id=my-agent");
|
|
1654
1656
|
|
|
1655
1657
|
// Down has Actions (Decision Spine)
|
|
1656
1658
|
const decisionSpine = m.down.find(
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* RFC 0006 § Connected rail / Empty-state.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type
|
|
18
|
+
import { type CausalProofTier, dashboardRouteToHref } from "@obsunified/types";
|
|
19
19
|
import { ActionConfidence } from "@obsunified/types/constants";
|
|
20
20
|
import type { CollectorPlugin } from "../framework/collector";
|
|
21
21
|
import { getChangeEvent } from "../lib/autosre-change-correlation";
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
import type { SqlDb } from "../lib/sql-db";
|
|
38
38
|
import { sqlDbFor } from "../lib/sql-db";
|
|
39
39
|
import { getProjectId } from "./_context";
|
|
40
|
+
import { respondWithContract } from "./autosre-routes";
|
|
40
41
|
|
|
41
42
|
import {
|
|
42
43
|
type ActionContextLinkMetadata,
|
|
@@ -456,7 +457,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
456
457
|
? [
|
|
457
458
|
{
|
|
458
459
|
label: `${traceIds.length} sampled traces`,
|
|
459
|
-
href:
|
|
460
|
+
href: dashboardRouteToHref({
|
|
461
|
+
tab: "traces",
|
|
462
|
+
traceId: traceIds[0],
|
|
463
|
+
}),
|
|
460
464
|
count: traceIds.length,
|
|
461
465
|
sample: traceIds[0],
|
|
462
466
|
},
|
|
@@ -556,7 +560,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
556
560
|
.slice(0, MAX_LINKS_INLINE)
|
|
557
561
|
.map((toolCall) => ({
|
|
558
562
|
label: `tool: ${toolCall.tool_name}`,
|
|
559
|
-
href:
|
|
563
|
+
href: dashboardRouteToHref({
|
|
564
|
+
tab: "tool-calls",
|
|
565
|
+
toolCallId: toolCall.id,
|
|
566
|
+
}),
|
|
560
567
|
entityKind: "tool_call",
|
|
561
568
|
entityId: toolCall.id,
|
|
562
569
|
...traceLevelMetadata("profile tool call"),
|
|
@@ -986,7 +993,7 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
986
993
|
links: [
|
|
987
994
|
{
|
|
988
995
|
label: `${run.agentName} (v${run.agentVersion})`,
|
|
989
|
-
href:
|
|
996
|
+
href: hrefForEntity("agent", run.agentId),
|
|
990
997
|
},
|
|
991
998
|
],
|
|
992
999
|
},
|
|
@@ -1070,7 +1077,7 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1070
1077
|
links: [
|
|
1071
1078
|
{
|
|
1072
1079
|
label: `${changeRow.targetNodeId} (${changeRow.environment})`,
|
|
1073
|
-
|
|
1080
|
+
...resolveFaultLocusTarget(changeRow.targetNodeId),
|
|
1074
1081
|
relationshipCertainty: "exact",
|
|
1075
1082
|
reason: "Target node mutated by this change event.",
|
|
1076
1083
|
},
|
|
@@ -1099,7 +1106,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1099
1106
|
label: "Attributed Findings",
|
|
1100
1107
|
links: findings.results.map((f) => ({
|
|
1101
1108
|
label: f.title,
|
|
1102
|
-
href:
|
|
1109
|
+
href: dashboardRouteToHref({
|
|
1110
|
+
tab: "findings",
|
|
1111
|
+
findingId: f.id,
|
|
1112
|
+
}),
|
|
1103
1113
|
entityKind: "finding",
|
|
1104
1114
|
entityId: f.id,
|
|
1105
1115
|
relationshipCertainty: "exact",
|
|
@@ -1137,7 +1147,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1137
1147
|
label: "Failing Spans on Cohort Version",
|
|
1138
1148
|
links: spans.results.map((s) => ({
|
|
1139
1149
|
label: `${s.service_name} · ${s.span_name}`,
|
|
1140
|
-
href:
|
|
1150
|
+
href: dashboardRouteToHref({
|
|
1151
|
+
tab: "traces",
|
|
1152
|
+
traceId: s.trace_id,
|
|
1153
|
+
spanId: s.span_id,
|
|
1154
|
+
}),
|
|
1141
1155
|
entityKind: "span",
|
|
1142
1156
|
entityId: `${s.trace_id}:${s.span_id}`,
|
|
1143
1157
|
relationshipCertainty: "exact",
|
|
@@ -1167,7 +1181,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1167
1181
|
links: [
|
|
1168
1182
|
{
|
|
1169
1183
|
label: `incident ${findingRow.incidentId}`,
|
|
1170
|
-
href:
|
|
1184
|
+
href: dashboardRouteToHref({
|
|
1185
|
+
tab: "incidents",
|
|
1186
|
+
incidentId: findingRow.incidentId,
|
|
1187
|
+
}),
|
|
1171
1188
|
entityKind: "incident",
|
|
1172
1189
|
entityId: findingRow.incidentId,
|
|
1173
1190
|
relationshipCertainty: "exact",
|
|
@@ -1182,7 +1199,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1182
1199
|
links: [
|
|
1183
1200
|
{
|
|
1184
1201
|
label: `change ${findingRow.contributingChangeId}`,
|
|
1185
|
-
href:
|
|
1202
|
+
href: dashboardRouteToHref({
|
|
1203
|
+
tab: "changes",
|
|
1204
|
+
changeId: findingRow.contributingChangeId,
|
|
1205
|
+
}),
|
|
1186
1206
|
entityKind: "change",
|
|
1187
1207
|
entityId: findingRow.contributingChangeId,
|
|
1188
1208
|
relationshipCertainty: "exact",
|
|
@@ -1206,7 +1226,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1206
1226
|
? [
|
|
1207
1227
|
{
|
|
1208
1228
|
label: `contending trace ${findingRow.contendingTraceId}`,
|
|
1209
|
-
href:
|
|
1229
|
+
href: dashboardRouteToHref({
|
|
1230
|
+
tab: "traces",
|
|
1231
|
+
traceId: findingRow.contendingTraceId,
|
|
1232
|
+
}),
|
|
1210
1233
|
entityKind: "trace" as const,
|
|
1211
1234
|
entityId: findingRow.contendingTraceId,
|
|
1212
1235
|
relationshipCertainty: "inferred" as const,
|
|
@@ -1280,7 +1303,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1280
1303
|
label: "Findings",
|
|
1281
1304
|
links: incFindings.results.map((f) => ({
|
|
1282
1305
|
label: f.title,
|
|
1283
|
-
href:
|
|
1306
|
+
href: dashboardRouteToHref({
|
|
1307
|
+
tab: "findings",
|
|
1308
|
+
findingId: f.id,
|
|
1309
|
+
}),
|
|
1284
1310
|
entityKind: "finding" as const,
|
|
1285
1311
|
entityId: f.id,
|
|
1286
1312
|
relationshipCertainty: "exact" as const,
|
|
@@ -1350,7 +1376,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1350
1376
|
];
|
|
1351
1377
|
}
|
|
1352
1378
|
|
|
1353
|
-
return
|
|
1379
|
+
return respondWithContract(
|
|
1380
|
+
c,
|
|
1381
|
+
"GET /internal/connected/:kind/:id",
|
|
1382
|
+
manifest,
|
|
1383
|
+
);
|
|
1354
1384
|
});
|
|
1355
1385
|
},
|
|
1356
1386
|
};
|