feat: add markdown failure cards
This commit is contained in:
parent
03d73b7fda
commit
c181bad94e
@ -33,6 +33,9 @@ export function renderMarkdownReport(report) {
|
||||
...Object.entries(report.summary.statuses).map(([status, count]) => `- ${status}: ${count}`),
|
||||
""
|
||||
);
|
||||
if (!report.gate) {
|
||||
lines.push(...formatRecordFailureCards(report.records));
|
||||
}
|
||||
if (report.gate) {
|
||||
lines.push(...formatGateSection(report.gate));
|
||||
}
|
||||
@ -464,6 +467,47 @@ function formatMetrics(metrics) {
|
||||
return lines.length > 0 ? lines : ["- unavailable"];
|
||||
}
|
||||
|
||||
function formatRecordFailureCards(records = []) {
|
||||
const cards = records
|
||||
.filter((record) => !["PASS", "DRY-RUN"].includes(record.status))
|
||||
.map(recordFailureCard);
|
||||
if (cards.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines = ["## Failure Cards", ""];
|
||||
for (const card of cards.slice(0, 8)) {
|
||||
lines.push(`- ${card.status} ${card.scenario}${card.state ? `/${card.state}` : ""}: ${card.summary}`);
|
||||
lines.push(` - likely owner: ${card.likelyOwner}`);
|
||||
if (card.command) {
|
||||
lines.push(` - command: \`${card.command}\``);
|
||||
}
|
||||
for (const item of card.evidence.slice(0, 4)) {
|
||||
lines.push(` - evidence: ${item}`);
|
||||
}
|
||||
}
|
||||
if (cards.length > 8) {
|
||||
lines.push(`- ${cards.length - 8} additional failure card(s) omitted from Markdown. See JSON report for full records.`);
|
||||
}
|
||||
lines.push("");
|
||||
return lines;
|
||||
}
|
||||
|
||||
function recordFailureCard(record) {
|
||||
const failed = firstFailedCommand(record);
|
||||
const violationMessages = (record.violations ?? []).map((violation) => violation.message);
|
||||
const summary = violationMessages[0] ?? summarizeFailureReason(failed) ?? `${record.status} ${record.scenario}`;
|
||||
return {
|
||||
status: record.status,
|
||||
scenario: record.scenario,
|
||||
state: record.state?.id ?? null,
|
||||
summary,
|
||||
likelyOwner: record.likelyOwner ?? "OpenClaw",
|
||||
command: failed?.command ? shortCommand(failed.command) : null,
|
||||
evidence: briefEvidence(record.measurements ?? {}, violationMessages)
|
||||
};
|
||||
}
|
||||
|
||||
function indentFence(value) {
|
||||
return [" ```text", ...value.trim().split("\n").slice(0, 80).map((line) => ` ${line}`), " ```"].join("\n");
|
||||
}
|
||||
|
||||
@ -206,6 +206,7 @@ export async function runSelfCheck(flags = {}) {
|
||||
checks.push(runtimeDepsLogParserCheck());
|
||||
checks.push(runtimeDepsWarmReuseEvaluationCheck());
|
||||
checks.push(await performanceBaselineCheck(tmp));
|
||||
checks.push(markdownFailureCardsCheck());
|
||||
checks.push(readinessClassificationCheck());
|
||||
checks.push(await resourceRoleAttributionCheck(tmp));
|
||||
checks.push(await processSnapshotCheck(tmp));
|
||||
@ -3034,6 +3035,67 @@ function thresholdPolicyCalibrationCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
function markdownFailureCardsCheck() {
|
||||
try {
|
||||
const rendered = renderMarkdownReport({
|
||||
generatedAt: "2026-05-01T00:00:00.000Z",
|
||||
runId: "self-check-failure-cards",
|
||||
mode: "execution",
|
||||
target: "runtime:stable",
|
||||
platform: { os: "test", release: "test", arch: "test", node: "test" },
|
||||
summary: { total: 1, statuses: { FAIL: 1 } },
|
||||
records: [{
|
||||
scenario: "gateway-performance",
|
||||
title: "Gateway Performance",
|
||||
status: "FAIL",
|
||||
target: "runtime:stable",
|
||||
envName: "kova-self-check",
|
||||
likelyOwner: "gateway-runtime",
|
||||
objective: "Synthetic failure card check",
|
||||
phases: [{
|
||||
id: "start",
|
||||
title: "Start",
|
||||
intent: "Start gateway",
|
||||
commands: ["ocm start kova-self-check --runtime stable --json"],
|
||||
evidence: [],
|
||||
results: [{
|
||||
command: "ocm start kova-self-check --runtime stable --json",
|
||||
status: 1,
|
||||
timedOut: false,
|
||||
durationMs: 45000,
|
||||
stdout: "",
|
||||
stderr: "gateway did not become healthy"
|
||||
}]
|
||||
}],
|
||||
measurements: {
|
||||
timeToHealthReadyMs: 45000,
|
||||
peakRssMb: 1100,
|
||||
resourceTopRolesByRss: [{ role: "gateway", peakRssMb: 1100, maxCpuPercent: 220 }]
|
||||
},
|
||||
violations: [{ message: "gateway readiness exceeded threshold" }]
|
||||
}]
|
||||
});
|
||||
assertEqual(rendered.includes("## Failure Cards"), true, "markdown failure cards section");
|
||||
assertEqual(rendered.includes("FAIL gateway-performance: gateway readiness exceeded threshold"), true, "failure card summary");
|
||||
assertEqual(rendered.includes("likely owner: gateway-runtime"), true, "failure card owner");
|
||||
assertEqual(rendered.includes("evidence: timeToHealthReadyMs: 45000"), true, "failure card evidence");
|
||||
return {
|
||||
id: "markdown-failure-cards",
|
||||
status: "PASS",
|
||||
command: "render synthetic failure Markdown",
|
||||
durationMs: 0
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
id: "markdown-failure-cards",
|
||||
status: "FAIL",
|
||||
command: "render synthetic failure Markdown",
|
||||
durationMs: 0,
|
||||
message: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function stateRegistryValidationCheck() {
|
||||
try {
|
||||
let rejectedTrait = false;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user