feat: add markdown failure cards

This commit is contained in:
Shakker 2026-05-01 10:45:03 +01:00
parent 03d73b7fda
commit c181bad94e
No known key found for this signature in database
2 changed files with 106 additions and 0 deletions

View File

@ -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");
}

View File

@ -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;