fix: improve workbench triad diagnostics
This commit is contained in:
@@ -3039,6 +3039,44 @@ function workbenchTriadMismatchKind(row) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function workbenchTriadRootCauseFromDrilldown(drilldown, summary = {}) {
|
||||
const groups = Array.isArray(drilldown?.groups) ? drilldown.groups : [];
|
||||
const hasStaleCompletedRail = groups.some((group) => group?.mismatchKind === "rail-card-status-mismatch" && String(group?.statusTuple || "").includes("rail=completed,card=running,final=false"));
|
||||
if (hasStaleCompletedRail) return {
|
||||
rootCause: "workbench_session_rail_status_stale_after_new_running_turn",
|
||||
rootCauseStatus: "confirmed-from-dom-samples",
|
||||
rootCauseConfidence: "high",
|
||||
dominantMismatchKind: "rail-card-status-mismatch",
|
||||
summary: "Workbench session rail kept the previous completed terminal status while a newer turn card was running and Final Response was absent",
|
||||
nextAction: "Inspect HWLAB frontend session status authority/reducer, especially workbench-server-state sessionStatusAuthorityFromMessages and SessionRail sessionToSessionTab status input; session rail must derive from the latest active turn/message authority rather than the previous sealed terminal message.",
|
||||
sourceOfTruth: "latest durable Workbench turn/message projection for the active session",
|
||||
valuesRedacted: true,
|
||||
};
|
||||
const finalMismatchCount = Number(summary?.cardFinalResponseMismatchCount ?? 0);
|
||||
const hasFinalMismatch = finalMismatchCount > 0 || groups.some((group) => /final=false/u.test(String(group?.statusTuple || "")) && group?.mismatchKind !== "rail-card-status-mismatch");
|
||||
if (hasFinalMismatch) return {
|
||||
rootCause: "workbench_terminal_final_response_not_sealed",
|
||||
rootCauseStatus: "confirmed-from-dom-samples",
|
||||
rootCauseConfidence: "high",
|
||||
dominantMismatchKind: "completed-card-final-response-absent",
|
||||
summary: "Workbench terminal turn card did not expose a structured Final Response body",
|
||||
nextAction: "Inspect HWLAB terminal message/finalResponse projection contract before changing renderer fallback behavior.",
|
||||
sourceOfTruth: "durable Workbench terminal message projection",
|
||||
valuesRedacted: true,
|
||||
};
|
||||
const mismatchKinds = Array.isArray(drilldown?.summary?.mismatchKinds) ? drilldown.summary.mismatchKinds : [];
|
||||
return {
|
||||
rootCause: "workbench_projection_state_triad_not_sealed",
|
||||
rootCauseStatus: "confirmed-from-dom-samples",
|
||||
rootCauseConfidence: "high",
|
||||
dominantMismatchKind: mismatchKinds[0] ?? "unknown",
|
||||
summary: "Workbench session rail status, turn card status, and Final Response body presence diverged from the allowed state tuples",
|
||||
nextAction: "Use drilldown.otelDrilldown.commands for the listed traceIds, then inspect staticSourceHints and add unit tests from unitTestReproHints before changing UI rendering.",
|
||||
sourceOfTruth: "durable Workbench projection/read model",
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function workbenchTriadTuple(row) {
|
||||
return [
|
||||
"rail=" + (row?.railStatus ?? "-"),
|
||||
@@ -3082,6 +3120,7 @@ function workbenchTriadStaticSourceHints() {
|
||||
|
||||
function workbenchTriadUnitTestReproHints() {
|
||||
return [
|
||||
"frontend reducer: when a second trace is running in the same session, session rail status must not stay on the previous completed terminal trace",
|
||||
"backend projector: terminal event must produce a single sealed turn tuple consumed by session list, session detail, messages and turn-status APIs",
|
||||
"backend read model: completed rail status must not coexist with running turn card or missing Final Response for the same trace",
|
||||
"frontend server-state merge: stale running/empty snapshots must not overwrite a sealed completed+Final Response turn",
|
||||
@@ -3194,6 +3233,7 @@ function normalizeWorkbenchTriadStatus(status, running = false) {
|
||||
if (running === true) return "running";
|
||||
if (!value) return null;
|
||||
if (/^(completed|complete|succeeded|success|finished|done|terminal|sealed)$/u.test(value)) return "completed";
|
||||
if (/^(failed|failure|error|blocked|timeout|canceled|cancelled|stale|thread-resume-failed|interrupted|expired|idle)$/u.test(value)) return "completed";
|
||||
if (/^(pending|running|active|busy|admitted|dispatching|executing|streaming|processing|queued|in-progress|creating)$/u.test(value)) return "running";
|
||||
return null;
|
||||
}
|
||||
@@ -3364,30 +3404,35 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
|
||||
...(Array.isArray(turnStateTriad.invalidFullTriads) ? turnStateTriad.invalidFullTriads : []),
|
||||
...(Array.isArray(turnStateTriad.cardFinalResponseMismatches) ? turnStateTriad.cardFinalResponseMismatches : [])
|
||||
];
|
||||
if (Number(turnStateTriadSummary.invalidRowCount ?? 0) > 0) findings.push({
|
||||
id: "workbench-turn-state-triad-inconsistent",
|
||||
severity: "red",
|
||||
summary: "Workbench session rail status, turn card status, and Final Response body presence diverged from the allowed running/running/absent or completed/completed/present tuples",
|
||||
count: turnStateTriadSummary.invalidRowCount,
|
||||
fullTriadCount: turnStateTriadSummary.fullTriadRowCount,
|
||||
invalidFullTriadCount: turnStateTriadSummary.invalidFullTriadCount,
|
||||
cardFinalResponseMismatchCount: turnStateTriadSummary.cardFinalResponseMismatchCount,
|
||||
legacyCollectorMissingCount: turnStateTriadSummary.collectorMissingRowCount,
|
||||
collectorMissingFields: Array.isArray(turnStateTriadSummary.collectorMissingFields) ? turnStateTriadSummary.collectorMissingFields : [],
|
||||
allowedTuples: [
|
||||
{ railStatus: "completed", cardStatus: "completed", finalResponsePresent: true },
|
||||
{ railStatus: "running", cardStatus: "running", finalResponsePresent: false }
|
||||
],
|
||||
samples: turnStateTriadRows.slice(0, 20),
|
||||
drilldown: turnStateTriad.drilldown ?? buildWorkbenchTurnStateTriadDrilldown(turnStateTriadRows),
|
||||
collectorMissingSamples: Array.isArray(turnStateTriad.collectorMissingRows) ? turnStateTriad.collectorMissingRows.slice(0, 10) : [],
|
||||
sourceOfTruth: "durable Workbench projection/read model; do not repair via DOM fallback or GET-side state mutation",
|
||||
nextAction: "Use drilldown.otelDrilldown.commands for the listed traceIds, then inspect staticSourceHints and add unit tests from unitTestReproHints before changing UI rendering.",
|
||||
rootCause: "workbench_projection_state_triad_not_sealed",
|
||||
rootCauseStatus: "confirmed-from-dom-samples",
|
||||
rootCauseConfidence: "high",
|
||||
valuesRedacted: true
|
||||
});
|
||||
if (Number(turnStateTriadSummary.invalidRowCount ?? 0) > 0) {
|
||||
const drilldown = turnStateTriad.drilldown ?? buildWorkbenchTurnStateTriadDrilldown(turnStateTriadRows);
|
||||
const rootCause = workbenchTriadRootCauseFromDrilldown(drilldown, turnStateTriadSummary);
|
||||
findings.push({
|
||||
id: "workbench-turn-state-triad-inconsistent",
|
||||
severity: "red",
|
||||
summary: rootCause.summary,
|
||||
count: turnStateTriadSummary.invalidRowCount,
|
||||
fullTriadCount: turnStateTriadSummary.fullTriadRowCount,
|
||||
invalidFullTriadCount: turnStateTriadSummary.invalidFullTriadCount,
|
||||
cardFinalResponseMismatchCount: turnStateTriadSummary.cardFinalResponseMismatchCount,
|
||||
legacyCollectorMissingCount: turnStateTriadSummary.collectorMissingRowCount,
|
||||
collectorMissingFields: Array.isArray(turnStateTriadSummary.collectorMissingFields) ? turnStateTriadSummary.collectorMissingFields : [],
|
||||
dominantMismatchKind: rootCause.dominantMismatchKind,
|
||||
allowedTuples: [
|
||||
{ railStatus: "completed", cardStatus: "completed", finalResponsePresent: true },
|
||||
{ railStatus: "running", cardStatus: "running", finalResponsePresent: false }
|
||||
],
|
||||
samples: turnStateTriadRows.slice(0, 20),
|
||||
drilldown,
|
||||
collectorMissingSamples: Array.isArray(turnStateTriad.collectorMissingRows) ? turnStateTriad.collectorMissingRows.slice(0, 10) : [],
|
||||
sourceOfTruth: rootCause.sourceOfTruth + "; do not repair via DOM fallback or GET-side state mutation",
|
||||
nextAction: rootCause.nextAction,
|
||||
rootCause: rootCause.rootCause,
|
||||
rootCauseStatus: rootCause.rootCauseStatus,
|
||||
rootCauseConfidence: rootCause.rootCauseConfidence,
|
||||
valuesRedacted: true
|
||||
});
|
||||
}
|
||||
const promptFailures = Array.isArray(promptNetwork?.rounds) ? promptNetwork.rounds.filter((item) => item.chatPostOk === false && !promptCommandHasAuthoritativeSubmitSideEffect(control, item)) : [];
|
||||
if (promptFailures.length > 0) findings.push({ id: "prompt-chat-submit-failed", severity: "red", summary: "sendPrompt command had no successful /v1/agent/chat or /v1/agent/chat/steer POST response in the sampling window", count: promptFailures.length, rounds: promptFailures.slice(0, 10) });
|
||||
const promptSteerRounds = Array.isArray(promptNetwork?.rounds) ? promptNetwork.rounds.filter((item) => item.steerUsed === true) : [];
|
||||
|
||||
Reference in New Issue
Block a user