fix: harden sentinel quick verify findings
This commit is contained in:
@@ -1187,17 +1187,37 @@ function safeDetailValue(value) {
|
|||||||
|
|
||||||
function checkDisplay(item) {
|
function checkDisplay(item) {
|
||||||
const rawCode = rawCheckCode(item);
|
const rawCode = rawCheckCode(item);
|
||||||
const registered = checkDisplayCatalog[rawCode];
|
const rawId = rawFindingId(item);
|
||||||
if (registered) return registered;
|
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 {
|
return {
|
||||||
code: stableCheckCode(rawCode),
|
code: serviceCode || displayCheckCode(rawId || rawCode) || stableCheckCode(rawCode),
|
||||||
title: safeUserText(item?.checkTitleZh || item?.check?.titleZh) || "未登记监测项",
|
title: safeUserText(item?.checkTitleZh || item?.check?.titleZh) || "未登记监测项",
|
||||||
summary: safeUserText(item?.checkSummaryZh || item?.summary || item?.evidenceSummary) || "已记录监测项详情,见报告原文。",
|
summary: safeUserText(item?.checkSummaryZh || item?.summary || item?.evidenceSummary) || "已记录监测项详情,见报告原文。",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function rawCheckCode(item) {
|
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) {
|
function stableCheckCode(value) {
|
||||||
|
|||||||
@@ -941,18 +941,49 @@ function collectObserveView(state: SentinelCicdState, observerId: string, view:
|
|||||||
if (turn !== null) args.push("--turn", String(turn));
|
if (turn !== null) args.push("--turn", String(turn));
|
||||||
const result = runChildCli(args, timeoutSeconds);
|
const result = runChildCli(args, timeoutSeconds);
|
||||||
const payload = cliDataPayload(result.parsed);
|
const payload = cliDataPayload(result.parsed);
|
||||||
const collect = record(payload.collect);
|
const collect = observeCollectPayload(payload);
|
||||||
|
const payloadData = record(payload.data);
|
||||||
return {
|
return {
|
||||||
ok: result.ok && result.parsed !== null && payload.ok !== false && collect.ok !== false,
|
ok: result.ok && result.parsed !== null && payload.ok !== false && collect.ok !== false,
|
||||||
view,
|
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,
|
collect,
|
||||||
payload,
|
payload,
|
||||||
|
collectShape: observeCollectShape(payload, collect),
|
||||||
result: result.result,
|
result: result.result,
|
||||||
valuesRedacted: true,
|
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 {
|
export function runChildCli(args: string[], timeoutSeconds: number, input?: string, env?: NodeJS.ProcessEnv): ChildCliResult {
|
||||||
const result = runCommand(["bun", "scripts/cli.ts", ...args], repoRoot, {
|
const result = runCommand(["bun", "scripts/cli.ts", ...args], repoRoot, {
|
||||||
input,
|
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>[] {
|
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 [];
|
if (quickVerifyHasDurableBusinessTurn(promptIndex, turnSummary, traceFrame)) return [];
|
||||||
|
const turnDiagnostics = quickVerifyTurnSummaryDiagnostics(promptIndex, turnSummary);
|
||||||
|
const traceDiagnostics = quickVerifyTraceFrameDiagnostics(traceFrame);
|
||||||
const rendered = [
|
const rendered = [
|
||||||
typeof turnSummary?.renderedText === "string" ? turnSummary.renderedText : "",
|
typeof turnSummary?.renderedText === "string" ? turnSummary.renderedText : "",
|
||||||
typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "",
|
typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "",
|
||||||
@@ -1425,6 +1458,13 @@ function quickVerifyControlFindings(failure: string | null, promptIndex: number,
|
|||||||
severity: "red",
|
severity: "red",
|
||||||
count: 1,
|
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.",
|
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,
|
failure: failure ?? null,
|
||||||
promptIndex,
|
promptIndex,
|
||||||
valuesRedacted: true,
|
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 {
|
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;
|
const scopedRows = promptIndex > 0 ? rows.filter((row) => numberAtNullable(row, "round") === promptIndex) : rows;
|
||||||
return scopedRows.find((row) => {
|
return scopedRows.find((row) => {
|
||||||
const finalResponse = record(row.finalResponse);
|
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> {
|
function quickVerifyTurnSummaryFallback(state: SentinelCicdState, observerId: string, promptIndex: number): Record<string, unknown> {
|
||||||
const turnSummary = collectObserveView(state, observerId, "turn-summary", null, 25);
|
const turnSummary = collectObserveView(state, observerId, "turn-summary", null, 25);
|
||||||
const row = quickVerifyCompletedTurnSummaryRow(promptIndex, turnSummary);
|
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) {
|
if (row === null) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -1479,6 +1519,7 @@ function quickVerifyTurnSummaryFallback(state: SentinelCicdState, observerId: st
|
|||||||
collectOk: turnSummary.ok === true,
|
collectOk: turnSummary.ok === true,
|
||||||
rowCount: rows.length,
|
rowCount: rows.length,
|
||||||
promptIndex,
|
promptIndex,
|
||||||
|
diagnostics: quickVerifyTurnSummaryDiagnostics(promptIndex, turnSummary),
|
||||||
result: turnSummary.result ?? null,
|
result: turnSummary.result ?? null,
|
||||||
valuesRedacted: true,
|
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 {
|
function quickVerifyHasDurableBusinessTurn(promptIndex: number, turnSummary: Record<string, unknown> | null, traceFrame: Record<string, unknown> | null): boolean {
|
||||||
if (quickVerifyCompletedTurnSummaryRow(promptIndex, turnSummary) !== null) return true;
|
if (quickVerifyCompletedTurnSummaryRow(promptIndex, turnSummary) !== null) return true;
|
||||||
|
if (quickVerifyTraceFrameHasDurableTurn(traceFrame)) return true;
|
||||||
const renderedTrace = typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "";
|
const renderedTrace = typeof traceFrame?.renderedText === "string" ? traceFrame.renderedText : "";
|
||||||
if (!renderedTrace) return false;
|
if (!renderedTrace) return false;
|
||||||
if (/Final Response\s*\n\s*\(空内容\)/iu.test(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);
|
&& !/无\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(
|
function quickVerifyBusinessStatus(
|
||||||
failure: string | null,
|
failure: string | null,
|
||||||
promptIndex: number,
|
promptIndex: number,
|
||||||
|
|||||||
Reference in New Issue
Block a user