fix: harden sentinel quick verify findings

This commit is contained in:
Codex
2026-06-30 20:31:07 +00:00
parent aa5ac9481e
commit 45f890bf7f
2 changed files with 129 additions and 8 deletions
@@ -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,