fix(workbench): tag read model not found spans (#1990)

Co-authored-by: root <root@lyon.remote>
This commit is contained in:
Lyon
2026-06-23 22:11:24 +08:00
committed by GitHub
parent 79e71db1fd
commit 0b6fc63472
+40 -2
View File
@@ -270,6 +270,33 @@ function attachWorkbenchRealtimeOtelContext(request, fields = {}) {
};
}
function attachWorkbenchReadModelDiagnosticOtel(request, fields = {}) {
const context = request?.hwlabHttpRequestContext;
if (!context) return;
const code = textValue(fields.code) || "workbench_read_model_not_found";
const route = textValue(fields.route) || context.route || "/v1/workbench";
context.route = route;
context.lastHttpError = {
code,
category: "workbench_read_model",
layer: "workbench.read_model"
};
context.otelAttributes = {
...(context.otelAttributes ?? {}),
"workbench.route": route,
"workbench.read_model.route": route,
"workbench.read_model.result": code,
"workbench.read_model.count": Number.isFinite(Number(fields.count)) ? Math.trunc(Number(fields.count)) : null,
"workbench.session_id": fields.sessionId ?? null,
"workbench.trace_id": fields.traceId ?? null,
"workbench.turn_id": fields.turnId ?? null,
traceId: fields.traceId ?? null,
"error.code": code,
"error.category": "workbench_read_model",
"error.layer": "workbench.read_model"
};
}
function emitWorkbenchRealtimeAcceptedOtelSpan(request, env = process.env) {
const context = request?.hwlabHttpRequestContext;
if (!context?.traceId) return;
@@ -1237,7 +1264,11 @@ async function handleWorkbenchSessionDetail(request, response, options, actor, s
const result = await queryFactsByRouteId(readModel, sessionId, { families: WORKBENCH_SESSION_DETAIL_FAMILIES });
if (result.error) return sendJson(response, 503, workbenchProjectionStoreError(result.error));
const session = visibleFactSessions(result.facts, actor)[0] ?? null;
if (!session) return sendJson(response, 404, workbenchError("workbench_session_not_found", "Workbench session is not visible to the current actor.", { sessionId }));
if (!session) {
const body = workbenchError("workbench_session_not_found", "Workbench session is not visible to the current actor.", { sessionId });
attachWorkbenchReadModelDiagnosticOtel(request, { route: "/v1/workbench/sessions/:id", code: "workbench_session_not_found", sessionId, count: result.count });
return sendJson(response, 404, body);
}
sendJson(response, 200, {
ok: true,
status: "found",
@@ -1253,7 +1284,11 @@ async function handleWorkbenchMessagePage(request, response, url, options, actor
const result = await queryFactsByRouteId(readModel, sessionId, { families: WORKBENCH_SESSION_MESSAGE_PAGE_FAMILIES });
if (result.error) return sendJson(response, 503, workbenchProjectionStoreError(result.error));
const session = visibleFactSessions(result.facts, actor)[0] ?? null;
if (!session) return sendJson(response, 404, workbenchError("workbench_session_not_found", "Workbench session is not visible to the current actor.", { sessionId }));
if (!session) {
const body = workbenchError("workbench_session_not_found", "Workbench session is not visible to the current actor.", { sessionId });
attachWorkbenchReadModelDiagnosticOtel(request, { route: "/v1/workbench/sessions/:id/messages", code: "workbench_session_not_found", sessionId, count: result.count });
return sendJson(response, 404, body);
}
const messages = factMessagesForSession(session, result.facts);
const limit = boundedLimit(url.searchParams.get("limit"));
const offset = cursorOffset(url.searchParams.get("cursor") ?? url.searchParams.get("after"));
@@ -1298,6 +1333,7 @@ async function handleWorkbenchTurnSnapshot(request, response, url, options, acto
const found = Boolean(session || turn);
if (!found) {
const body = workbenchError("workbench_turn_not_found", "Workbench turn is not visible to the current actor.", { turnId, traceId });
attachWorkbenchReadModelDiagnosticOtel(request, { route: "/v1/workbench/turns/:id", code: "workbench_turn_not_found", traceId, turnId, count: result.count });
recordWorkbenchTurnReadMetric(options, url, 404, body, startedAt);
return sendJson(response, 404, body);
}
@@ -1351,8 +1387,10 @@ async function handleWorkbenchTraceEventPage(request, response, url, options, ac
if (contextSession || contextTurn) {
const projection = factProjectionForTrace(context.facts, traceId);
const blocker = traceEventsReadModelBlocker("workbench_trace_metadata_missing", "Workbench trace metadata is missing from the trace events read model.", { traceId, projection, session: contextSession, turn: contextTurn, route: url.pathname });
attachWorkbenchReadModelDiagnosticOtel(request, { route: "/v1/workbench/traces/:id/events", code: "workbench_trace_metadata_missing", sessionId: factSessionId(contextSession), traceId, count: context.count });
return sendJson(response, 404, workbenchTraceEventsReadModelError(blocker, { traceId, projection, session: contextSession, turn: contextTurn }));
}
attachWorkbenchReadModelDiagnosticOtel(request, { route: "/v1/workbench/traces/:id/events", code: "workbench_trace_not_found", traceId, count: context.count });
return sendJson(response, 404, workbenchError("workbench_trace_not_found", "Workbench trace is not visible to the current actor.", { traceId }));
}
const projection = factProjectionForTrace(metadataFacts, traceId);