fix: harden sentinel quick verify findings
This commit is contained in:
@@ -1187,17 +1187,37 @@ function safeDetailValue(value) {
|
||||
|
||||
function checkDisplay(item) {
|
||||
const rawCode = rawCheckCode(item);
|
||||
const registered = checkDisplayCatalog[rawCode];
|
||||
if (registered) return registered;
|
||||
const rawId = rawFindingId(item);
|
||||
const registered = checkDisplayCatalog[rawId] || checkDisplayCatalog[rawCode];
|
||||
const serviceCode = displayCheckCode(item?.checkCode || item?.check?.code);
|
||||
if (registered) {
|
||||
return {
|
||||
...registered,
|
||||
code: serviceCode || registered.code,
|
||||
title: safeUserText(item?.checkTitleZh || item?.check?.titleZh) || registered.title,
|
||||
summary: safeUserText(item?.checkSummaryZh || item?.summary || item?.evidenceSummary || item?.check?.summaryZh) || registered.summary,
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: stableCheckCode(rawCode),
|
||||
code: serviceCode || displayCheckCode(rawId || rawCode) || stableCheckCode(rawCode),
|
||||
title: safeUserText(item?.checkTitleZh || item?.check?.titleZh) || "未登记监测项",
|
||||
summary: safeUserText(item?.checkSummaryZh || item?.summary || item?.evidenceSummary) || "已记录监测项详情,见报告原文。",
|
||||
};
|
||||
}
|
||||
|
||||
function rawCheckCode(item) {
|
||||
return String(item?.checkCode || item?.check?.code || item?.code || item?.findingId || item?.kind || "unknown");
|
||||
return String(item?.checkCode || item?.check?.code || rawFindingId(item) || "unknown");
|
||||
}
|
||||
|
||||
function rawFindingId(item) {
|
||||
const value = item?.findingId || item?.finding_id || item?.id || item?.kind || item?.code;
|
||||
return value === null || value === undefined || value === "" ? "" : String(value);
|
||||
}
|
||||
|
||||
function displayCheckCode(value) {
|
||||
const text = String(value || "").replace(/\s+/g, " ").trim();
|
||||
if (text.length === 0 || text === "unknown") return "";
|
||||
return text;
|
||||
}
|
||||
|
||||
function stableCheckCode(value) {
|
||||
|
||||
@@ -941,18 +941,49 @@ function collectObserveView(state: SentinelCicdState, observerId: string, view:
|
||||
if (turn !== null) args.push("--turn", String(turn));
|
||||
const result = runChildCli(args, timeoutSeconds);
|
||||
const payload = cliDataPayload(result.parsed);
|
||||
const collect = record(payload.collect);
|
||||
const collect = observeCollectPayload(payload);
|
||||
const payloadData = record(payload.data);
|
||||
return {
|
||||
ok: result.ok && result.parsed !== null && payload.ok !== false && collect.ok !== false,
|
||||
view,
|
||||
renderedText: typeof collect.renderedText === "string" ? collect.renderedText : typeof payload.renderedText === "string" ? payload.renderedText : String(record(result.result).stdoutTail ?? record(result.result).stdoutPreview ?? ""),
|
||||
renderedText: typeof collect.renderedText === "string"
|
||||
? collect.renderedText
|
||||
: typeof payload.renderedText === "string"
|
||||
? payload.renderedText
|
||||
: typeof payloadData.renderedText === "string"
|
||||
? payloadData.renderedText
|
||||
: String(record(result.result).stdoutTail ?? record(result.result).stdoutPreview ?? ""),
|
||||
collect,
|
||||
payload,
|
||||
collectShape: observeCollectShape(payload, collect),
|
||||
result: result.result,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function observeCollectPayload(payload: Record<string, unknown>): Record<string, unknown> {
|
||||
const candidates = [
|
||||
record(payload.collect),
|
||||
record(record(payload.data).collect),
|
||||
record(record(record(payload.data).data).collect),
|
||||
];
|
||||
return candidates.find((item) => Object.keys(item).length > 0) ?? {};
|
||||
}
|
||||
|
||||
function observeCollectShape(payload: Record<string, unknown>, collect: Record<string, unknown>): Record<string, unknown> {
|
||||
const direct = record(payload.collect);
|
||||
const nested = record(record(payload.data).collect);
|
||||
return {
|
||||
payloadKeys: Object.keys(payload).slice(0, 12),
|
||||
directCollect: Object.keys(direct).length > 0,
|
||||
nestedDataCollect: Object.keys(nested).length > 0,
|
||||
collectKeys: Object.keys(collect).slice(0, 12),
|
||||
rowCount: Array.isArray(collect.rows) ? collect.rows.length : null,
|
||||
traceId: stringAtNullable(collect, "traceId"),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function runChildCli(args: string[], timeoutSeconds: number, input?: string, env?: NodeJS.ProcessEnv): ChildCliResult {
|
||||
const result = runCommand(["bun", "scripts/cli.ts", ...args], repoRoot, {
|
||||
input,
|
||||
@@ -1397,6 +1428,8 @@ function observerCommandFailureBlocks(item: Record<string, unknown>): boolean {
|
||||
|
||||
function quickVerifyControlFindings(failure: string | null, promptIndex: number, turnSummary: Record<string, unknown> | null, traceFrame: Record<string, unknown> | null): Record<string, unknown>[] {
|
||||
if (quickVerifyHasDurableBusinessTurn(promptIndex, turnSummary, traceFrame)) return [];
|
||||
const turnDiagnostics = quickVerifyTurnSummaryDiagnostics(promptIndex, turnSummary);
|
||||
const traceDiagnostics = quickVerifyTraceFrameDiagnostics(traceFrame);
|
||||
const rendered = [
|
||||
typeof turnSummary?.renderedText === "string" ? turnSummary.renderedText : "",
|
||||
typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "",
|
||||
@@ -1425,6 +1458,13 @@ function quickVerifyControlFindings(failure: string | null, promptIndex: number,
|
||||
severity: "red",
|
||||
count: 1,
|
||||
summary: "quick verify did not reach a durable business turn/session/trace rows/final response; public dashboard health cannot be treated as HWLAB recovery.",
|
||||
rootCause: `quick verify could not confirm a durable completed turn: turn-summary scopedRows=${String(turnDiagnostics.scopedRowCount ?? 0)} rowCount=${String(turnDiagnostics.rowCount ?? 0)}, traceFrame traceIdPresent=${traceDiagnostics.traceIdPresent === true} finalResponseEmpty=${traceDiagnostics.finalResponseEmpty === true}.`,
|
||||
rootCauseStatus: "confirmed",
|
||||
rootCauseConfidence: "high",
|
||||
evidenceSummary: `turn-summary rows=${String(turnDiagnostics.rowCount ?? 0)} scoped=${String(turnDiagnostics.scopedRowCount ?? 0)} traceFrameTrace=${traceDiagnostics.traceIdPresent === true ? "present" : "missing"} finalResponseBytes=${String(traceDiagnostics.finalResponseBytes ?? "-")}`,
|
||||
nextAction: "Inspect the structured turnSummary/traceFrame diagnostics first; if rows exist with completed non-empty final responses, fix sentinel interpretation instead of treating HWLAB Web as blocked.",
|
||||
turnSummaryDiagnostics: turnDiagnostics,
|
||||
traceFrameDiagnostics: traceDiagnostics,
|
||||
failure: failure ?? null,
|
||||
promptIndex,
|
||||
valuesRedacted: true,
|
||||
@@ -1458,7 +1498,7 @@ function enrichObserveStartFailureFinding(finding: Record<string, unknown>, resu
|
||||
}
|
||||
|
||||
function quickVerifyCompletedTurnSummaryRow(promptIndex: number, turnSummary: Record<string, unknown> | null): Record<string, unknown> | null {
|
||||
const rows = Array.isArray(record(turnSummary?.collect).rows) ? record(turnSummary?.collect).rows.map(record) : [];
|
||||
const rows = quickVerifyTurnSummaryRows(turnSummary);
|
||||
const scopedRows = promptIndex > 0 ? rows.filter((row) => numberAtNullable(row, "round") === promptIndex) : rows;
|
||||
return scopedRows.find((row) => {
|
||||
const finalResponse = record(row.finalResponse);
|
||||
@@ -1471,7 +1511,7 @@ function quickVerifyCompletedTurnSummaryRow(promptIndex: number, turnSummary: Re
|
||||
function quickVerifyTurnSummaryFallback(state: SentinelCicdState, observerId: string, promptIndex: number): Record<string, unknown> {
|
||||
const turnSummary = collectObserveView(state, observerId, "turn-summary", null, 25);
|
||||
const row = quickVerifyCompletedTurnSummaryRow(promptIndex, turnSummary);
|
||||
const rows = Array.isArray(record(turnSummary.collect).rows) ? record(turnSummary.collect).rows.map(record) : [];
|
||||
const rows = quickVerifyTurnSummaryRows(turnSummary);
|
||||
if (row === null) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -1479,6 +1519,7 @@ function quickVerifyTurnSummaryFallback(state: SentinelCicdState, observerId: st
|
||||
collectOk: turnSummary.ok === true,
|
||||
rowCount: rows.length,
|
||||
promptIndex,
|
||||
diagnostics: quickVerifyTurnSummaryDiagnostics(promptIndex, turnSummary),
|
||||
result: turnSummary.result ?? null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
@@ -1501,6 +1542,7 @@ function quickVerifyTurnSummaryFallback(state: SentinelCicdState, observerId: st
|
||||
|
||||
function quickVerifyHasDurableBusinessTurn(promptIndex: number, turnSummary: Record<string, unknown> | null, traceFrame: Record<string, unknown> | null): boolean {
|
||||
if (quickVerifyCompletedTurnSummaryRow(promptIndex, turnSummary) !== null) return true;
|
||||
if (quickVerifyTraceFrameHasDurableTurn(traceFrame)) return true;
|
||||
const renderedTrace = typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "";
|
||||
if (!renderedTrace) return false;
|
||||
if (/Final Response\s*\n\s*\(空内容\)/iu.test(renderedTrace)) return false;
|
||||
@@ -1509,6 +1551,65 @@ function quickVerifyHasDurableBusinessTurn(promptIndex: number, turnSummary: Rec
|
||||
&& !/无\s*trace\s*rows|no\s+trace\s+rows|traceId=-|routeSession=-|activeSession=-/iu.test(renderedTrace);
|
||||
}
|
||||
|
||||
function quickVerifyTurnSummaryRows(turnSummary: Record<string, unknown> | null): Record<string, unknown>[] {
|
||||
const collect = record(turnSummary?.collect);
|
||||
return Array.isArray(collect.rows) ? collect.rows.map(record) : [];
|
||||
}
|
||||
|
||||
function quickVerifyTraceFrameHasDurableTurn(traceFrame: Record<string, unknown> | null): boolean {
|
||||
const collect = record(traceFrame?.collect);
|
||||
if (collect.ok === false) return false;
|
||||
if (stringAtNullable(collect, "traceId") === null) return false;
|
||||
const finalResponse = record(collect.finalResponse);
|
||||
if (finalResponse.empty === true) return false;
|
||||
const textBytes = numberAtNullable(finalResponse, "textBytes");
|
||||
return textBytes === null || textBytes > 0;
|
||||
}
|
||||
|
||||
function quickVerifyTraceFrameDiagnostics(traceFrame: Record<string, unknown> | null): Record<string, unknown> {
|
||||
const collect = record(traceFrame?.collect);
|
||||
const finalResponse = record(collect.finalResponse);
|
||||
return {
|
||||
collectOk: traceFrame?.ok === true,
|
||||
collectShape: record(traceFrame?.collectShape),
|
||||
collectView: stringAtNullable(collect, "view"),
|
||||
traceIdPresent: stringAtNullable(collect, "traceId") !== null,
|
||||
finalResponseEmpty: finalResponse.empty === true,
|
||||
finalResponseBytes: numberAtNullable(finalResponse, "textBytes"),
|
||||
blockerPresent: collect.blocker !== null && collect.blocker !== undefined,
|
||||
result: traceFrame?.result ?? null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function quickVerifyTurnSummaryDiagnostics(promptIndex: number, turnSummary: Record<string, unknown> | null): Record<string, unknown> {
|
||||
const collect = record(turnSummary?.collect);
|
||||
const rows = quickVerifyTurnSummaryRows(turnSummary);
|
||||
const scopedRows = promptIndex > 0 ? rows.filter((row) => numberAtNullable(row, "round") === promptIndex) : rows;
|
||||
const scopedStatuses = scopedRows.slice(0, 4).map((row) => {
|
||||
const finalResponse = record(row.finalResponse);
|
||||
return {
|
||||
round: numberAtNullable(row, "round"),
|
||||
status: stringAtNullable(row, "status"),
|
||||
traceIdPresent: stringAtNullable(row, "traceId") !== null,
|
||||
finalResponseEmpty: finalResponse.empty === true,
|
||||
finalResponseBytes: numberAtNullable(finalResponse, "textBytes"),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
});
|
||||
return {
|
||||
collectOk: turnSummary?.ok === true,
|
||||
collectShape: record(turnSummary?.collectShape),
|
||||
collectView: stringAtNullable(collect, "view"),
|
||||
rowCount: rows.length,
|
||||
scopedRowCount: scopedRows.length,
|
||||
promptIndex,
|
||||
scopedStatuses,
|
||||
result: turnSummary?.result ?? null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function quickVerifyBusinessStatus(
|
||||
failure: string | null,
|
||||
promptIndex: number,
|
||||
|
||||
Reference in New Issue
Block a user