@obsunified/collector 2.2.1 → 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/auth.js +1 -1
- package/dist/{chunk-KMEZKGSO.js → chunk-2FALTDCE.js} +12 -4
- package/dist/chunk-2FALTDCE.js.map +1 -0
- package/dist/{chunk-ZSF5XP3B.js → chunk-RASSTGNB.js} +30 -22
- package/dist/chunk-RASSTGNB.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +2892 -2240
- 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 -3
- package/src/__tests__/autosre-closed-loop.test.ts +700 -0
- package/src/__tests__/db-parity.integration.test.ts +961 -0
- package/src/__tests__/internal-contracts-routes.test.ts +451 -0
- package/src/auth/dashboard-auth.test.ts +61 -0
- package/src/auth/dashboard-auth.ts +17 -2
- package/src/framework/env.ts +105 -0
- package/src/index.ts +13 -4
- package/src/lib/ai-store/calls.ts +43 -23
- package/src/lib/ai-store/evaluations.test.ts +3 -3
- package/src/lib/ai-store.ts +12 -1
- package/src/lib/analyses-store.test.ts +6 -6
- package/src/lib/autosre-findings.ts +169 -14
- package/src/lib/autosre-remediation-gate.ts +156 -26
- 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/projects-store.test.ts +38 -1
- package/src/lib/projects-store.ts +23 -4
- package/src/lib/sql-db-postgres.integration.test.ts +75 -2
- package/src/lib/stage6.test.ts +1 -1
- package/src/lib/usage-store.ts +9 -3
- package/src/migrations/052_remediation_callback.sql +10 -0
- package/src/migrations/053_remediation_callback_delivery.sql +7 -0
- package/src/migrations/parity.test.ts +105 -3
- package/src/migrations-postgres/052_remediation_callback.sql +10 -0
- package/src/migrations-postgres/053_remediation_callback_delivery.sql +7 -0
- package/src/plugins/ai-call-by-id.test.ts +73 -0
- package/src/plugins/ai-receiver.ts +15 -0
- package/src/plugins/autosre-routes.ts +409 -147
- package/src/plugins/connected-routes/deep-link-shapes.test.ts +44 -0
- package/src/plugins/connected-routes/manifest.test.ts +224 -0
- package/src/plugins/connected-routes/manifest.ts +131 -28
- package/src/plugins/connected-routes-links.test.ts +96 -0
- package/src/plugins/connected-routes.test.ts +13 -11
- package/src/plugins/connected-routes.ts +53 -20
- package/src/plugins/eval-cases-routes.ts +33 -10
- package/src/plugins/evidence-retrieval-routes.ts +89 -24
- package/dist/chunk-KMEZKGSO.js.map +0 -1
- package/dist/chunk-ZSF5XP3B.js.map +0 -1
- /package/dist/{openai-GSJZGFGL.js.map → openai-VDKVVZAV.js.map} +0 -0
|
@@ -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
|
});
|
|
@@ -470,7 +470,7 @@ describe("ConnectedRail manifest — raw signal action back-links", () => {
|
|
|
470
470
|
m.down.find((s) => s.label === "Evaluations for this span")?.links[0],
|
|
471
471
|
).toMatchObject({
|
|
472
472
|
label: "eval: groundedness (passed)",
|
|
473
|
-
href: "#/
|
|
473
|
+
href: "#/evaluations/eval-explicit",
|
|
474
474
|
});
|
|
475
475
|
expect(
|
|
476
476
|
m.related.find((s) => s.label === "Agent runs for this span")?.links[0],
|
|
@@ -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,
|
|
@@ -749,7 +749,7 @@ describe("ConnectedRail manifest — raw signal action back-links", () => {
|
|
|
749
749
|
).toMatchObject({ href: "#/tool-calls/tool-explicit" });
|
|
750
750
|
expect(
|
|
751
751
|
m.down.find((s) => s.label === "Evaluations for this AI call")?.links[0],
|
|
752
|
-
).toMatchObject({ href: "#/
|
|
752
|
+
).toMatchObject({ href: "#/evaluations/eval-explicit" });
|
|
753
753
|
expect(
|
|
754
754
|
m.related.find((s) => s.label === "Agent runs for this AI call")
|
|
755
755
|
?.links[0],
|
|
@@ -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",
|
|
@@ -1280,7 +1282,7 @@ describe("ConnectedRail scenario acceptance contracts", () => {
|
|
|
1280
1282
|
const aiSection = userManifest.across.find((s) =>
|
|
1281
1283
|
s.label.toLowerCase().includes("ai call"),
|
|
1282
1284
|
);
|
|
1283
|
-
expect(aiSection?.links[0].href).toBe("#/ai
|
|
1285
|
+
expect(aiSection?.links[0].href).toBe("#/ai/calls/ai-heavy-1");
|
|
1284
1286
|
|
|
1285
1287
|
const sessionManifest = await fetch(
|
|
1286
1288
|
"/internal/connected/usage/sess-heavy?session_id=sess-heavy",
|
|
@@ -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,11 +37,13 @@ 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,
|
|
43
44
|
type ConnectedEntityKind,
|
|
44
45
|
type ConnectedManifest,
|
|
46
|
+
hrefForEntity,
|
|
45
47
|
KNOWN_KINDS,
|
|
46
48
|
linksFromActions,
|
|
47
49
|
linksFromAi,
|
|
@@ -59,6 +61,7 @@ import {
|
|
|
59
61
|
linkToTrace,
|
|
60
62
|
MAX_LINKS_INLINE,
|
|
61
63
|
profileLinksForTrace,
|
|
64
|
+
resolveFaultLocusTarget,
|
|
62
65
|
} from "./connected-routes/manifest";
|
|
63
66
|
|
|
64
67
|
export type {
|
|
@@ -454,7 +457,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
454
457
|
? [
|
|
455
458
|
{
|
|
456
459
|
label: `${traceIds.length} sampled traces`,
|
|
457
|
-
href:
|
|
460
|
+
href: dashboardRouteToHref({
|
|
461
|
+
tab: "traces",
|
|
462
|
+
traceId: traceIds[0],
|
|
463
|
+
}),
|
|
458
464
|
count: traceIds.length,
|
|
459
465
|
sample: traceIds[0],
|
|
460
466
|
},
|
|
@@ -554,7 +560,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
554
560
|
.slice(0, MAX_LINKS_INLINE)
|
|
555
561
|
.map((toolCall) => ({
|
|
556
562
|
label: `tool: ${toolCall.tool_name}`,
|
|
557
|
-
href:
|
|
563
|
+
href: dashboardRouteToHref({
|
|
564
|
+
tab: "tool-calls",
|
|
565
|
+
toolCallId: toolCall.id,
|
|
566
|
+
}),
|
|
558
567
|
entityKind: "tool_call",
|
|
559
568
|
entityId: toolCall.id,
|
|
560
569
|
...traceLevelMetadata("profile tool call"),
|
|
@@ -984,7 +993,7 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
984
993
|
links: [
|
|
985
994
|
{
|
|
986
995
|
label: `${run.agentName} (v${run.agentVersion})`,
|
|
987
|
-
href:
|
|
996
|
+
href: hrefForEntity("agent", run.agentId),
|
|
988
997
|
},
|
|
989
998
|
],
|
|
990
999
|
},
|
|
@@ -1068,7 +1077,7 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1068
1077
|
links: [
|
|
1069
1078
|
{
|
|
1070
1079
|
label: `${changeRow.targetNodeId} (${changeRow.environment})`,
|
|
1071
|
-
|
|
1080
|
+
...resolveFaultLocusTarget(changeRow.targetNodeId),
|
|
1072
1081
|
relationshipCertainty: "exact",
|
|
1073
1082
|
reason: "Target node mutated by this change event.",
|
|
1074
1083
|
},
|
|
@@ -1097,7 +1106,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1097
1106
|
label: "Attributed Findings",
|
|
1098
1107
|
links: findings.results.map((f) => ({
|
|
1099
1108
|
label: f.title,
|
|
1100
|
-
href:
|
|
1109
|
+
href: dashboardRouteToHref({
|
|
1110
|
+
tab: "findings",
|
|
1111
|
+
findingId: f.id,
|
|
1112
|
+
}),
|
|
1101
1113
|
entityKind: "finding",
|
|
1102
1114
|
entityId: f.id,
|
|
1103
1115
|
relationshipCertainty: "exact",
|
|
@@ -1115,10 +1127,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1115
1127
|
const spans = changeRow.version
|
|
1116
1128
|
? await db
|
|
1117
1129
|
.prepare(
|
|
1118
|
-
`SELECT
|
|
1130
|
+
`SELECT trace_id, span_id, service_name, span_name
|
|
1119
1131
|
FROM telemetry_spans
|
|
1120
1132
|
WHERE project_id = ? AND environment = ? AND service_version = ? AND status_code = 2
|
|
1121
|
-
|
|
1133
|
+
GROUP BY trace_id, span_id, service_name, span_name
|
|
1134
|
+
ORDER BY MAX(start_time) DESC LIMIT 5`,
|
|
1122
1135
|
)
|
|
1123
1136
|
.bind(projectId, changeRow.environment, changeRow.version)
|
|
1124
1137
|
.all<{
|
|
@@ -1134,7 +1147,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1134
1147
|
label: "Failing Spans on Cohort Version",
|
|
1135
1148
|
links: spans.results.map((s) => ({
|
|
1136
1149
|
label: `${s.service_name} · ${s.span_name}`,
|
|
1137
|
-
href:
|
|
1150
|
+
href: dashboardRouteToHref({
|
|
1151
|
+
tab: "traces",
|
|
1152
|
+
traceId: s.trace_id,
|
|
1153
|
+
spanId: s.span_id,
|
|
1154
|
+
}),
|
|
1138
1155
|
entityKind: "span",
|
|
1139
1156
|
entityId: `${s.trace_id}:${s.span_id}`,
|
|
1140
1157
|
relationshipCertainty: "exact",
|
|
@@ -1164,7 +1181,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1164
1181
|
links: [
|
|
1165
1182
|
{
|
|
1166
1183
|
label: `incident ${findingRow.incidentId}`,
|
|
1167
|
-
href:
|
|
1184
|
+
href: dashboardRouteToHref({
|
|
1185
|
+
tab: "incidents",
|
|
1186
|
+
incidentId: findingRow.incidentId,
|
|
1187
|
+
}),
|
|
1168
1188
|
entityKind: "incident",
|
|
1169
1189
|
entityId: findingRow.incidentId,
|
|
1170
1190
|
relationshipCertainty: "exact",
|
|
@@ -1179,7 +1199,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1179
1199
|
links: [
|
|
1180
1200
|
{
|
|
1181
1201
|
label: `change ${findingRow.contributingChangeId}`,
|
|
1182
|
-
href:
|
|
1202
|
+
href: dashboardRouteToHref({
|
|
1203
|
+
tab: "changes",
|
|
1204
|
+
changeId: findingRow.contributingChangeId,
|
|
1205
|
+
}),
|
|
1183
1206
|
entityKind: "change",
|
|
1184
1207
|
entityId: findingRow.contributingChangeId,
|
|
1185
1208
|
relationshipCertainty: "exact",
|
|
@@ -1195,7 +1218,7 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1195
1218
|
links: [
|
|
1196
1219
|
{
|
|
1197
1220
|
label: `fault locus: ${findingRow.faultLocusEntityId}`,
|
|
1198
|
-
|
|
1221
|
+
...resolveFaultLocusTarget(findingRow.faultLocusEntityId),
|
|
1199
1222
|
relationshipCertainty: "exact",
|
|
1200
1223
|
causalProofTier: proofTier,
|
|
1201
1224
|
},
|
|
@@ -1203,7 +1226,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1203
1226
|
? [
|
|
1204
1227
|
{
|
|
1205
1228
|
label: `contending trace ${findingRow.contendingTraceId}`,
|
|
1206
|
-
href:
|
|
1229
|
+
href: dashboardRouteToHref({
|
|
1230
|
+
tab: "traces",
|
|
1231
|
+
traceId: findingRow.contendingTraceId,
|
|
1232
|
+
}),
|
|
1207
1233
|
entityKind: "trace" as const,
|
|
1208
1234
|
entityId: findingRow.contendingTraceId,
|
|
1209
1235
|
relationshipCertainty: "inferred" as const,
|
|
@@ -1249,11 +1275,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1249
1275
|
links: [
|
|
1250
1276
|
{
|
|
1251
1277
|
label: `${incRow.symptomAnchorKind}: ${incRow.symptomAnchorId}`,
|
|
1252
|
-
href:
|
|
1253
|
-
|
|
1254
|
-
incRow.
|
|
1255
|
-
|
|
1256
|
-
|
|
1278
|
+
href: hrefForEntity(
|
|
1279
|
+
incRow.symptomAnchorKind,
|
|
1280
|
+
incRow.symptomAnchorId,
|
|
1281
|
+
),
|
|
1282
|
+
entityKind: incRow.symptomAnchorKind,
|
|
1257
1283
|
entityId: incRow.symptomAnchorId,
|
|
1258
1284
|
relationshipCertainty: "exact",
|
|
1259
1285
|
},
|
|
@@ -1277,7 +1303,10 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1277
1303
|
label: "Findings",
|
|
1278
1304
|
links: incFindings.results.map((f) => ({
|
|
1279
1305
|
label: f.title,
|
|
1280
|
-
href:
|
|
1306
|
+
href: dashboardRouteToHref({
|
|
1307
|
+
tab: "findings",
|
|
1308
|
+
findingId: f.id,
|
|
1309
|
+
}),
|
|
1281
1310
|
entityKind: "finding" as const,
|
|
1282
1311
|
entityId: f.id,
|
|
1283
1312
|
relationshipCertainty: "exact" as const,
|
|
@@ -1347,7 +1376,11 @@ export const connectedRoutesPlugin: CollectorPlugin = {
|
|
|
1347
1376
|
];
|
|
1348
1377
|
}
|
|
1349
1378
|
|
|
1350
|
-
return
|
|
1379
|
+
return respondWithContract(
|
|
1380
|
+
c,
|
|
1381
|
+
"GET /internal/connected/:kind/:id",
|
|
1382
|
+
manifest,
|
|
1383
|
+
);
|
|
1351
1384
|
});
|
|
1352
1385
|
},
|
|
1353
1386
|
};
|
|
@@ -3,7 +3,10 @@ import type {
|
|
|
3
3
|
EvidenceReferenceContract as EvidenceReferenceContractType,
|
|
4
4
|
JsonValue,
|
|
5
5
|
} from "@obsunified/types";
|
|
6
|
-
import {
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
593
|
+
return respondWithContract(c, "GET /internal/eval-cases/:id/results", {
|
|
594
|
+
evalCaseResults,
|
|
595
|
+
});
|
|
573
596
|
});
|
|
574
597
|
},
|
|
575
598
|
};
|
|
@@ -11,6 +11,10 @@ import type {
|
|
|
11
11
|
TelemetrySpanDetail,
|
|
12
12
|
TelemetryTraceDetailResponse,
|
|
13
13
|
} from "@obsunified/types";
|
|
14
|
+
import {
|
|
15
|
+
buildFindingFingerprint,
|
|
16
|
+
dashboardRouteToHref,
|
|
17
|
+
} from "@obsunified/types";
|
|
14
18
|
import type { CollectorPlugin } from "../framework/collector";
|
|
15
19
|
import {
|
|
16
20
|
computeChangeBlastRadius,
|
|
@@ -18,6 +22,7 @@ import {
|
|
|
18
22
|
listChangeEvents,
|
|
19
23
|
} from "../lib/autosre-change-correlation";
|
|
20
24
|
import {
|
|
25
|
+
fingerprintInputFromSpan,
|
|
21
26
|
getInvestigationFinding,
|
|
22
27
|
searchInvestigationFindings,
|
|
23
28
|
} from "../lib/autosre-findings";
|
|
@@ -39,6 +44,7 @@ import { LogsStore } from "../lib/logs-store";
|
|
|
39
44
|
import { decodePprofBlob, summarizeProfileFrames } from "../lib/parse-pprof";
|
|
40
45
|
import type { SqlDb } from "../lib/sql-db";
|
|
41
46
|
import { getProjectId } from "./_context";
|
|
47
|
+
import { respondWithContract } from "./autosre-routes";
|
|
42
48
|
import { fetchReplayChunks } from "./replay-query-routes";
|
|
43
49
|
|
|
44
50
|
interface EvidenceRefPayload {
|
|
@@ -156,24 +162,35 @@ const estimateTokens = (value: unknown): number =>
|
|
|
156
162
|
Math.ceil(JSON.stringify(value).length / 4);
|
|
157
163
|
|
|
158
164
|
const traceRoute = (traceId: string) =>
|
|
159
|
-
|
|
165
|
+
dashboardRouteToHref({ tab: "traces", traceId });
|
|
160
166
|
const spanRoute = (traceId: string, spanId: string) =>
|
|
161
|
-
|
|
162
|
-
const logRoute = (logId: string) =>
|
|
167
|
+
dashboardRouteToHref({ tab: "traces", traceId, spanId });
|
|
168
|
+
const logRoute = (logId: string) =>
|
|
169
|
+
dashboardRouteToHref({ tab: "logs", logId });
|
|
163
170
|
const actionRoute = (actionId: string) =>
|
|
164
|
-
|
|
171
|
+
dashboardRouteToHref({ tab: "actions", actionId });
|
|
165
172
|
const agentRunRoute = (agentRunId: string) =>
|
|
166
|
-
|
|
173
|
+
dashboardRouteToHref({ tab: "agent-runs", agentRunId });
|
|
167
174
|
const toolCallRoute = (toolCallId: string) =>
|
|
168
|
-
|
|
175
|
+
dashboardRouteToHref({ tab: "tool-calls", toolCallId });
|
|
169
176
|
const replayRoute = (sessionId: string) =>
|
|
170
|
-
|
|
177
|
+
dashboardRouteToHref({ tab: "replay", sessionId });
|
|
171
178
|
const profileRoute = (profileId: string, traceId?: string) =>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
dashboardRouteToHref({
|
|
180
|
+
tab: "profiles",
|
|
181
|
+
profileId,
|
|
182
|
+
...(traceId ? { traceId } : {}),
|
|
183
|
+
});
|
|
175
184
|
const aiCallRoute = (callId: string) =>
|
|
176
|
-
|
|
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 });
|
|
177
194
|
|
|
178
195
|
const evidenceRef = (
|
|
179
196
|
evidenceId: string,
|
|
@@ -1067,7 +1084,7 @@ const actionEvidenceReferences = (
|
|
|
1067
1084
|
`eval:${evalResult.id}`,
|
|
1068
1085
|
"eval",
|
|
1069
1086
|
evalResult.id,
|
|
1070
|
-
|
|
1087
|
+
evalRoute(evalResult.id),
|
|
1071
1088
|
"evidence.bundle.eval_results",
|
|
1072
1089
|
evalResult.passed ? 0.75 : 0.9,
|
|
1073
1090
|
`Eval ${evalResult.evaluatorName} ${evalResult.passed ? "passed" : "failed"}${evalResult.score === null ? "" : ` with score ${evalResult.score}`}.`,
|
|
@@ -1607,6 +1624,34 @@ const attachAutoSreTriageContext = async (
|
|
|
1607
1624
|
limit: 5,
|
|
1608
1625
|
});
|
|
1609
1626
|
}
|
|
1627
|
+
// Secondary match: the same recurrence fingerprint (service + route +
|
|
1628
|
+
// error signature) in this project, so a failure that recurs on a
|
|
1629
|
+
// different span id still surfaces the findings and fixes recorded
|
|
1630
|
+
// before. Entity-id matches stay first.
|
|
1631
|
+
if (faultLocus?.faultLocusSpan) {
|
|
1632
|
+
const spanFp = fingerprintInputFromSpan(
|
|
1633
|
+
faultLocus.faultLocusSpan,
|
|
1634
|
+
environment,
|
|
1635
|
+
);
|
|
1636
|
+
const fingerprint = buildFindingFingerprint({
|
|
1637
|
+
...spanFp,
|
|
1638
|
+
normalizedOperation: spanFp.normalizedOperation ?? "",
|
|
1639
|
+
errorSignature: spanFp.errorSignature ?? "",
|
|
1640
|
+
}).fingerprint;
|
|
1641
|
+
const byFingerprint = await searchInvestigationFindings(db, {
|
|
1642
|
+
projectId,
|
|
1643
|
+
environment,
|
|
1644
|
+
fingerprint,
|
|
1645
|
+
limit: 5,
|
|
1646
|
+
});
|
|
1647
|
+
const seen = new Set(priorFindingsList.map((f) => f.id));
|
|
1648
|
+
for (const f of byFingerprint) {
|
|
1649
|
+
if (!seen.has(f.id) && priorFindingsList.length < 5) {
|
|
1650
|
+
priorFindingsList.push(f);
|
|
1651
|
+
seen.add(f.id);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1610
1655
|
// No unfiltered fallback: unrelated findings would read as relevant
|
|
1611
1656
|
// prior art. An empty list is the honest answer.
|
|
1612
1657
|
const priorFindings = priorFindingsList.map((f) => ({
|
|
@@ -1621,7 +1666,7 @@ const attachAutoSreTriageContext = async (
|
|
|
1621
1666
|
|
|
1622
1667
|
for (const br of blastRadii) {
|
|
1623
1668
|
const changeId = br.change.id;
|
|
1624
|
-
const changeRoute =
|
|
1669
|
+
const changeRoute = changeEntityRoute(changeId);
|
|
1625
1670
|
bundle.evidenceReferences.push({
|
|
1626
1671
|
evidenceId: `change:${changeId}`,
|
|
1627
1672
|
entityKind: "change",
|
|
@@ -1655,7 +1700,7 @@ const attachAutoSreTriageContext = async (
|
|
|
1655
1700
|
}
|
|
1656
1701
|
|
|
1657
1702
|
for (const finding of priorFindingsList) {
|
|
1658
|
-
const findingRoute =
|
|
1703
|
+
const findingRoute = findingEntityRoute(finding.id);
|
|
1659
1704
|
bundle.evidenceReferences.push({
|
|
1660
1705
|
evidenceId: `finding:${finding.id}`,
|
|
1661
1706
|
entityKind: "finding",
|
|
@@ -1867,7 +1912,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
1867
1912
|
runtime.logger,
|
|
1868
1913
|
);
|
|
1869
1914
|
}
|
|
1870
|
-
return
|
|
1915
|
+
return respondWithContract(
|
|
1916
|
+
c,
|
|
1917
|
+
"POST /internal/evidence/bundle",
|
|
1918
|
+
await materializeBundle(db, projectId, bundle),
|
|
1919
|
+
);
|
|
1871
1920
|
}
|
|
1872
1921
|
|
|
1873
1922
|
if (anchor.entityKind === "incident") {
|
|
@@ -1889,7 +1938,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
1889
1938
|
{ kind: incident.symptomAnchorKind, id: incident.symptomAnchorId },
|
|
1890
1939
|
);
|
|
1891
1940
|
|
|
1892
|
-
const incRoute =
|
|
1941
|
+
const incRoute = incidentEntityRoute(incident.id);
|
|
1893
1942
|
const baseBundle: EvidenceBundle = {
|
|
1894
1943
|
schemaVersion: BUNDLE_SCHEMA_VERSION,
|
|
1895
1944
|
intent,
|
|
@@ -1958,7 +2007,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
1958
2007
|
baseBundle,
|
|
1959
2008
|
runtime.logger,
|
|
1960
2009
|
);
|
|
1961
|
-
return
|
|
2010
|
+
return respondWithContract(
|
|
2011
|
+
c,
|
|
2012
|
+
"POST /internal/evidence/bundle",
|
|
2013
|
+
await materializeBundle(db, projectId, enriched),
|
|
2014
|
+
);
|
|
1962
2015
|
}
|
|
1963
2016
|
|
|
1964
2017
|
if (anchor.entityKind === "change") {
|
|
@@ -1982,7 +2035,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
1982
2035
|
null,
|
|
1983
2036
|
);
|
|
1984
2037
|
|
|
1985
|
-
const changeRoute =
|
|
2038
|
+
const changeRoute = changeEntityRoute(change.id);
|
|
1986
2039
|
const summary =
|
|
1987
2040
|
blastRadius?.summary ??
|
|
1988
2041
|
`Change ${change.id} (${change.kind} ${change.version ?? ""}) in ${change.environment}: ${change.summary}`;
|
|
@@ -2071,7 +2124,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2071
2124
|
baseBundle,
|
|
2072
2125
|
runtime.logger,
|
|
2073
2126
|
);
|
|
2074
|
-
return
|
|
2127
|
+
return respondWithContract(
|
|
2128
|
+
c,
|
|
2129
|
+
"POST /internal/evidence/bundle",
|
|
2130
|
+
await materializeBundle(db, projectId, enriched),
|
|
2131
|
+
);
|
|
2075
2132
|
}
|
|
2076
2133
|
|
|
2077
2134
|
if (anchor.entityKind === "finding") {
|
|
@@ -2099,7 +2156,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2099
2156
|
{ kind: finding.symptomEntityKind, id: finding.symptomEntityId },
|
|
2100
2157
|
);
|
|
2101
2158
|
|
|
2102
|
-
const findingRoute =
|
|
2159
|
+
const findingRoute = findingEntityRoute(finding.id);
|
|
2103
2160
|
const summary = `${finding.title} (${finding.causalProofTier}, verification=${finding.causalVerificationStatus}, recovery=${finding.recoveryStatus}): ${finding.summary}`;
|
|
2104
2161
|
const baseBundle: EvidenceBundle = {
|
|
2105
2162
|
schemaVersion: BUNDLE_SCHEMA_VERSION,
|
|
@@ -2189,7 +2246,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2189
2246
|
baseBundle,
|
|
2190
2247
|
runtime.logger,
|
|
2191
2248
|
);
|
|
2192
|
-
return
|
|
2249
|
+
return respondWithContract(
|
|
2250
|
+
c,
|
|
2251
|
+
"POST /internal/evidence/bundle",
|
|
2252
|
+
await materializeBundle(db, projectId, enriched),
|
|
2253
|
+
);
|
|
2193
2254
|
}
|
|
2194
2255
|
|
|
2195
2256
|
if (anchor.entityKind !== "trace") {
|
|
@@ -2236,7 +2297,11 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2236
2297
|
runtime.logger,
|
|
2237
2298
|
);
|
|
2238
2299
|
}
|
|
2239
|
-
return
|
|
2300
|
+
return respondWithContract(
|
|
2301
|
+
c,
|
|
2302
|
+
"POST /internal/evidence/bundle",
|
|
2303
|
+
await materializeBundle(db, projectId, bundle),
|
|
2304
|
+
);
|
|
2240
2305
|
});
|
|
2241
2306
|
|
|
2242
2307
|
app.get("/internal/evidence/stats", async (c) => {
|
|
@@ -2328,7 +2393,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2328
2393
|
expanded_at: string;
|
|
2329
2394
|
}>(),
|
|
2330
2395
|
]);
|
|
2331
|
-
return c
|
|
2396
|
+
return respondWithContract(c, "GET /internal/evidence/stats", {
|
|
2332
2397
|
projectId,
|
|
2333
2398
|
generatedAt: new Date().toISOString(),
|
|
2334
2399
|
byKind: byKind.results.map((row) => ({
|
|
@@ -2366,7 +2431,7 @@ export const evidenceRetrievalRoutesPlugin: CollectorPlugin = {
|
|
|
2366
2431
|
})),
|
|
2367
2432
|
});
|
|
2368
2433
|
} catch {
|
|
2369
|
-
return c
|
|
2434
|
+
return respondWithContract(c, "GET /internal/evidence/stats", {
|
|
2370
2435
|
projectId,
|
|
2371
2436
|
generatedAt: new Date().toISOString(),
|
|
2372
2437
|
byKind: [],
|