fix(ci): ignore profiling-only resource gate failures

This commit is contained in:
Vincent Koc 2026-07-09 07:22:17 -07:00
parent 9775f09194
commit 7e2d287db5
No known key found for this signature in database
4 changed files with 1335 additions and 240 deletions

View file

@ -372,7 +372,7 @@ jobs:
then
effective_status=0
{
echo "Kova returned a partial release-gate verdict for filtered performance coverage, but all selected scenarios passed and no baseline regression was reported."
echo "Kova returned a non-zero release-gate verdict, but the trusted report adapter found only filtered coverage or profiling-affected resource thresholds with no baseline regression."
echo
} >> "$GITHUB_STEP_SUMMARY"
fi

View file

@ -1,102 +1,542 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
function numericCount(value) {
if (typeof value !== "number") {
return undefined;
const SCHEMA = {
report: "kova.report.v1",
gate: "kova.gate.v1",
performance: "kova.performance.v1",
profiling: "kova.profiling.v1",
baseline: "kova.baselineComparison.v1",
gateBaseline: "kova.gateBaselineSummary.v1",
};
const PROFILED_INTERPRETATION =
"instrumented run; CPU/RSS can include profiler and diagnostic overhead";
const RSS_METRICS = ["peakRssMb", "resourcePeakGatewayRssMb"];
const CPU_METRICS = ["cpuPercentMax"];
const DIRECT_VIOLATIONS = new Set(["cpuPercentMax", "peakRssMb"]);
const SOAK_VIOLATIONS = new Set(["gatewayRssGrowthMb", "rssGrowthMb"]);
const ROLE_VIOLATION = /^resourceByRole\.([^.]+)\.(maxCpuPercent|peakRssMb)$/u;
function check(condition, reason) {
if (!condition) {
throw new Error(reason);
}
return Number.isFinite(value) ? value : undefined;
}
const rssMetricIds = ["peakRssMb", "resourcePeakGatewayRssMb"];
const cpuMetricIds = ["cpuPercentMax"];
function object(value, label) {
check(value !== null && typeof value === "object" && !Array.isArray(value), `invalid ${label}`);
return value;
}
function hasSampledMetric(group, metricIds) {
return metricIds.some((metricId) => {
const metric = group?.metrics?.[metricId];
return metric && Number(metric.count) > 0;
});
function array(value, label) {
check(Array.isArray(value), `invalid ${label}`);
return value;
}
function count(value, label, { positive = false } = {}) {
check(Number.isSafeInteger(value) && value >= (positive ? 1 : 0), `invalid ${label}`);
return value;
}
function text(value, label) {
check(typeof value === "string" && value.trim().length > 0, `invalid ${label}`);
return value;
}
function finite(value, label) {
check(typeof value === "number" && Number.isFinite(value), `invalid ${label}`);
return value;
}
function stateId(record) {
return text(object(record.state, "record state").id, "record state id");
}
function recordKey(record) {
return [
text(record.scenario, "record scenario"),
text(record.surface, "record surface"),
stateId(record),
].join("\u0000");
}
function statusCounts(records) {
const statuses = {};
for (const record of records) {
text(record.status, "record status");
statuses[record.status] = (statuses[record.status] ?? 0) + 1;
}
return statuses;
}
function exactCounts(actualValue, expected, label) {
const actual = object(actualValue, label);
check(Object.keys(actual).length === Object.keys(expected).length, `${label} keys did not match`);
for (const [key, expectedCount] of Object.entries(expected)) {
check(count(actual[key], `${label}.${key}`) === expectedCount, `${label}.${key} did not match`);
}
}
function exactStrings(actualValue, expected, label) {
const actual = array(actualValue, label);
check(
actual.length === expected.length && actual.every((value, index) => value === expected[index]),
`${label} did not match`,
);
}
function validateCommandResult(value, label) {
const result = object(value, label);
check(result.status === 0 && result.timedOut === false, `${label} failed`);
}
function alreadyAbsent(result, noun) {
check(
Number.isSafeInteger(result.status) && result.status !== 0,
`${noun} cleanup status was invalid`,
);
check(result.timedOut === false, `${noun} cleanup timed out`);
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
const missing = new RegExp(`\\b${noun}\\b[\\s\\S]*\\b(?:does not exist|not found)\\b`, "iu").test(
output,
);
check(missing, `${noun} cleanup lacked already-absent evidence`);
}
function validateCleanup(status, resultValue, successStatus, noun, label) {
const result = object(resultValue, `${label} result`);
if (status === successStatus) {
validateCommandResult(result, `${label} result`);
return;
}
check(status === "already-absent", `${label} was not completed`);
alreadyAbsent(result, noun);
}
function validateRecord(recordValue) {
const record = object(recordValue, "record");
recordKey(record);
object(record.measurements, "record measurements");
const profiling = object(record.profiling, "record profiling");
check(profiling.schemaVersion === SCHEMA.profiling, "wrong profiling schema");
const phases = array(record.phases, "record phases");
check(phases.length > 0, "record had no phases");
for (const phaseValue of phases) {
const results = array(object(phaseValue, "phase").results, "phase results");
check(results.length > 0, "phase had no command results");
results.forEach((result, index) => validateCommandResult(result, `phase result ${index}`));
}
validateCleanup(
record.cleanup,
record.cleanupResult,
"destroyed",
"environment",
"record cleanup",
);
return record;
}
function validateTargetCleanup(report) {
if (!text(report.target, "report target").startsWith("local-build:")) {
check(report.targetCleanup === null, "non-local target had cleanup metadata");
return;
}
const cleanup = object(report.targetCleanup, "target cleanup");
validateCleanup(cleanup.status, cleanup.result, "removed", "runtime", "target cleanup");
}
function validateBaselines(report, gate) {
const reportBaseline = report.baseline;
const gateBaseline = gate.baseline;
check(
(reportBaseline === null || reportBaseline === undefined) ===
(gateBaseline === null || gateBaseline === undefined),
"baseline evidence was one-sided",
);
if (reportBaseline === null || reportBaseline === undefined) {
return;
}
const comparison = object(
object(reportBaseline, "report baseline").comparison,
"baseline comparison",
);
const summary = object(gateBaseline, "gate baseline");
check(comparison.schemaVersion === SCHEMA.baseline, "wrong baseline comparison schema");
check(summary.schemaVersion === SCHEMA.gateBaseline, "wrong gate baseline schema");
for (const [field, listField] of [
["regressionCount", "regressions"],
["missingBaselineCount", "missing"],
]) {
check(count(comparison[field], `baseline ${field}`) === 0, `baseline ${field} was nonzero`);
check(
array(comparison[listField], `baseline ${listField}`).length === 0,
`baseline ${listField} present`,
);
check(
count(summary[field], `gate baseline ${field}`) === 0,
`gate baseline ${field} was nonzero`,
);
const gateList = field === "regressionCount" ? "regressedGroups" : listField;
check(
array(summary[gateList], `gate baseline ${gateList}`).length === 0,
`gate baseline ${gateList} present`,
);
}
check(comparison.ok === true && summary.ok === true, "baseline was not ok");
check(
count(comparison.baselineEntryCount, "baseline entry count") ===
count(summary.baselineEntryCount, "gate baseline entry count"),
"baseline entry counts did not match",
);
}
function validateSummary(report, records) {
const summary = object(report.summary, "report summary");
check(
count(summary.total, "summary total") === records.length,
"summary total did not match records",
);
exactCounts(summary.statuses, statusCounts(records), "summary statuses");
}
function validateMetric(metricValue, sampleCount, label) {
const metric = object(metricValue, label);
const samples = array(metric.samples, `${label} samples`);
const metricCount = count(metric.count, `${label} count`, { positive: true });
check(metricCount === samples.length && metricCount <= sampleCount, `${label} count drift`);
check(
metric.classification === "stable" || metric.classification === "unstable",
`${label} classification was invalid`,
);
samples.forEach((sample) => finite(sample, `${label} sample`));
return samples;
}
function sampledMetric(metrics, ids, sampleCount, label) {
for (const id of ids) {
if (metrics[id] !== undefined) {
const samples = validateMetric(metrics[id], sampleCount, `${label}.${id}`);
check(samples.length === sampleCount, `${label}.${id} did not cover every record`);
return { id, samples };
}
}
throw new Error(`${label} was not sampled`);
}
function measured(record, ids, label) {
for (const id of ids) {
if (record.measurements[id] !== undefined) {
return finite(record.measurements[id], `${label}.${id}`);
}
}
throw new Error(`${label} was not measured`);
}
function validatePerformance(report, records, repeat) {
const performance = object(report.performance, "performance");
check(performance.schemaVersion === SCHEMA.performance, "wrong performance schema");
check(performance.repeat === repeat, "performance repeat did not match controls");
const groups = array(performance.groups, "performance groups");
check(
count(performance.groupCount, "performance group count") === groups.length,
"group count drift",
);
const recordsByKey = new Map();
for (const record of records) {
const key = recordKey(record);
const matching = recordsByKey.get(key) ?? [];
matching.push(record);
recordsByKey.set(key, matching);
}
check(groups.length === recordsByKey.size, "performance groups did not map to records");
const groupsByKey = new Map();
let profiledCount = 0;
for (const groupValue of groups) {
const group = object(groupValue, "performance group");
const identity = [
text(group.scenario, "group scenario"),
text(group.surface, "group surface"),
text(group.state, "group state"),
];
const key = identity.join("\u0000");
check(
group.key === identity.join("|") && !groupsByKey.has(key),
"performance group identity was invalid",
);
const matching = recordsByKey.get(key);
check(matching?.length > 0, "performance group had no records");
check(
count(group.sampleCount, "group sample count", { positive: true }) === matching.length,
"sample count drift",
);
exactCounts(group.statuses, statusCounts(matching), "group statuses");
const matchingProfiled = matching.filter((record) => record.profiling.enabled === true).length;
check(
count(group.profiledRunCount, "group profiled count") === matchingProfiled,
"profile count drift",
);
check(
group.resourceInterpretation === (matchingProfiled > 0 ? "instrumented" : "normal"),
"group resource interpretation was invalid",
);
profiledCount += matchingProfiled;
const metrics = object(group.metrics, "group metrics");
Object.entries(metrics).forEach(([id, metric]) =>
validateMetric(metric, group.sampleCount, `group metric ${id}`),
);
const rss = sampledMetric(metrics, RSS_METRICS, group.sampleCount, "group RSS");
const cpu = sampledMetric(metrics, CPU_METRICS, group.sampleCount, "group CPU");
for (const record of matching) {
check(
rss.samples.includes(measured(record, RSS_METRICS, "record RSS")),
"record RSS was not sampled",
);
check(
cpu.samples.includes(measured(record, CPU_METRICS, "record CPU")),
"record CPU was not sampled",
);
}
groupsByKey.set(key, group);
}
check(
count(performance.profiledRunCount, "performance profiled count") === profiledCount,
"profile total drift",
);
const unstableCount = groups.filter((group) =>
Object.values(group.metrics).some((metric) => metric.classification === "unstable"),
).length;
check(
count(performance.unstableGroupCount, "unstable group count") === unstableCount,
"unstable group count drift",
);
return groupsByKey;
}
function validateGateCards(gate) {
const cards = array(gate.cards, "gate cards");
const severities = { blocking: 0, warning: 0, info: 0 };
for (const cardValue of cards) {
const card = object(cardValue, "gate card");
const severity = card.severity;
check(Object.hasOwn(severities, severity), "unknown gate card severity");
if (severity === "info") {
check(
(card.kind === "filtered-required-scenario" ||
card.kind === "filtered-required-coverage") &&
card.status === "MISSING",
"unexpected info gate card",
);
}
if (severity === "warning") {
check(
card.kind === "missing-required-coverage" && card.status === "MISSING",
"unexpected warning gate card",
);
}
severities[severity] += 1;
}
check(
count(gate.blockingCount, "blocking count") === severities.blocking,
"blocking count drift",
);
check(count(gate.warningCount, "warning count") === severities.warning, "warning count drift");
check(count(gate.infoCount, "info count") === severities.info, "info count drift");
check(
count(gate.missingRequiredCount, "missing required count") === severities.info,
"missing count drift",
);
return cards;
}
function validateEnvelope(reportValue) {
const report = object(reportValue, "report");
const gate = object(report.gate, "gate");
const controls = object(report.controls, "controls");
check(
report.schemaVersion === SCHEMA.report && report.mode === "execution",
"wrong report schema or mode",
);
check(
gate.schemaVersion === SCHEMA.gate && gate.enabled === true,
"wrong or disabled gate schema",
);
check(controls.gate === true, "gate controls were disabled");
const filters = [
...array(controls.include, "include filters"),
...array(controls.exclude, "exclude filters"),
];
check(
filters.length > 0 &&
filters.every((filter) => typeof filter === "string" && filter.trim().length > 0),
"report filters were invalid",
);
const repeat = count(controls.repeat, "repeat", { positive: true });
check(
gate.partial === true && gate.complete === false && gate.ok === false,
"gate metadata was not partial",
);
const records = array(report.records, "records").map(validateRecord);
check(records.length > 0, "report had no records");
const cards = validateGateCards(gate);
validateBaselines(report, gate);
validateSummary(report, records);
validateTargetCleanup(report);
const groups = validatePerformance(report, records, repeat);
return { report, gate, records, cards, groups };
}
function deepProfiled(record) {
const profiling = record.profiling;
const measurements = record.measurements;
return [
[profiling.enabled, true],
[profiling.deepProfile, true],
[profiling.nodeProfile, true],
[profiling.heapSnapshot, true],
[profiling.diagnosticReport, true],
[profiling.profileOnFailure, false],
[profiling.affectsResourceMeasurements, true],
[profiling.baselineEligible, false],
[profiling.interpretation, PROFILED_INTERPRETATION],
[measurements.profilingEnabled, true],
[measurements.profilingAffectsResourceMeasurements, true],
[measurements.profilingBaselineEligible, false],
[measurements.profilingResourceInterpretation, PROFILED_INTERPRETATION],
].every(([actual, expected]) => actual === expected);
}
function violationMeasurement(record, violation) {
const metric = text(violation.metric, "violation metric");
if (violation.kind === "threshold" && DIRECT_VIOLATIONS.has(metric)) {
return finite(record.measurements[metric], `measurement ${metric}`);
}
if (violation.kind === "soak" && SOAK_VIOLATIONS.has(metric)) {
return finite(record.measurements[metric], `measurement ${metric}`);
}
const match = violation.kind === "resource" ? ROLE_VIOLATION.exec(metric) : null;
check(match !== null && violation.role === match[1], "role violation identity was invalid");
const role = object(
object(record.measurements.resourceByRole, "role measurements")[match[1]],
"role measurement",
);
return finite(role[match[2]], `role measurement ${metric}`);
}
function validateProfiledFailure(record, card, group) {
check(deepProfiled(record), "failed record was not canonical deep profiling");
check(
group.resourceInterpretation === "instrumented",
"failed record group was not instrumented",
);
const violations = array(record.violations, "record violations");
check(violations.length > 0, "failed record had no violations");
for (const violationValue of violations) {
const violation = object(violationValue, "violation");
const actual = finite(violation.actual, "violation actual");
check(actual === violationMeasurement(record, violation), "violation evidence drift");
if (violation.kind === "threshold") {
const metric = text(violation.metric, "violation metric");
const samples = validateMetric(
object(group.metrics, "group metrics")[metric],
group.sampleCount,
`violation group metric ${metric}`,
);
check(samples.includes(actual), "violation was not sampled in its performance group");
}
text(violation.expected, "violation expectation");
text(violation.message, "violation message");
}
const messages = violations.map((violation) => violation.message);
check(
card.kind === "openclaw-failure" &&
card.status === "FAIL" &&
card.failedCommand === null &&
card.scenario === record.scenario &&
card.state === stateId(record),
"blocking card identity was invalid",
);
check(card.summary === messages[0], "blocking card summary did not match");
exactStrings(card.violations, messages, "blocking card violations");
const cardMeasurements = object(card.measurements, "blocking card measurements");
check(
cardMeasurements.peakRssMb === record.measurements.peakRssMb &&
cardMeasurements.cpuPercentMax === record.measurements.cpuPercentMax,
"blocking card measurements did not match",
);
}
function evaluate(evaluator) {
try {
evaluator();
return { ok: true };
} catch (error) {
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
}
}
export function evaluateToleratedPartialKovaReport(report) {
const gate = report?.gate;
if (!gate || typeof gate !== "object" || Array.isArray(gate)) {
return { ok: false, reason: "missing gate metadata" };
}
if (gate.verdict !== "PARTIAL") {
return { ok: false, reason: `gate verdict was ${JSON.stringify(gate.verdict)}` };
}
const blockingCount = numericCount(gate.blockingCount);
if (blockingCount === undefined) {
return { ok: false, reason: "missing blocking count" };
}
if (blockingCount !== 0) {
return { ok: false, reason: `blocking count was ${JSON.stringify(gate.blockingCount)}` };
}
const reportBaseline = report?.baseline;
const gateBaseline = report?.gate?.baseline;
if (
(reportBaseline !== null && reportBaseline !== undefined) ||
(gateBaseline !== null && gateBaseline !== undefined)
) {
const baselineRegressionCount = numericCount(
reportBaseline?.comparison?.regressionCount ?? gateBaseline?.regressionCount,
return evaluate(() => {
const { gate, records, cards } = validateEnvelope(report);
check(gate.verdict === "PARTIAL", "gate verdict was not PARTIAL");
check(gate.blockingCount === 0, "PARTIAL gate had blocking cards");
check(
cards.every((card) => card.severity !== "blocking"),
"PARTIAL gate had a blocking card",
);
if (baselineRegressionCount === undefined) {
return { ok: false, reason: "missing baseline regression count" };
check(
records.every((record) => record.status === "PASS"),
"PARTIAL report had a non-PASS record",
);
check(
records.every((record) => array(record.violations, "record violations").length === 0),
"PARTIAL report had violations",
);
});
}
export function evaluateToleratedProfiledKovaReport(report) {
return evaluate(() => {
const { gate, records, cards, groups } = validateEnvelope(report);
check(gate.verdict === "DO_NOT_SHIP", "gate verdict was not DO_NOT_SHIP");
check(
records.every((record) => record.status === "PASS" || record.status === "FAIL"),
"invalid record status",
);
check(
records
.filter((record) => record.status === "PASS")
.every((record) => array(record.violations, "record violations").length === 0),
"PASS record had violations",
);
const failed = records.filter((record) => record.status === "FAIL");
const blocking = cards.filter((card) => card.severity === "blocking");
check(
failed.length > 0 && blocking.length === failed.length,
"failure/card counts did not match",
);
const remaining = [...blocking];
for (const record of failed) {
const index = remaining.findIndex(
(card) => card.scenario === record.scenario && card.state === stateId(record),
);
check(index >= 0, "blocking cards did not map one-to-one");
const card = remaining.splice(index, 1)[0];
validateProfiledFailure(record, card, groups.get(recordKey(record)));
}
if (baselineRegressionCount !== 0) {
return {
ok: false,
reason: `baseline regression count was ${JSON.stringify(baselineRegressionCount)}`,
};
}
}
check(remaining.length === 0, "blocking cards did not map one-to-one");
});
}
const statuses = report?.summary?.statuses;
if (!statuses || typeof statuses !== "object" || Array.isArray(statuses)) {
return { ok: false, reason: "missing status summary" };
export function evaluateToleratedKovaReport(report) {
const partial = evaluateToleratedPartialKovaReport(report);
if (partial.ok) {
return { ok: true, classification: "filtered-partial" };
}
const passCount = numericCount(statuses.PASS);
if (passCount === undefined || passCount <= 0) {
return { ok: false, reason: "status summary had no PASS records" };
const profiled = evaluateToleratedProfiledKovaReport(report);
if (profiled.ok) {
return { ok: true, classification: "profiled-resource-only" };
}
const nonPassStatuses = Object.entries(statuses).filter(
([status, count]) => status !== "PASS" && (numericCount(count) ?? 0) > 0,
);
if (nonPassStatuses.length > 0) {
return {
ok: false,
reason: `non-pass statuses present: ${nonPassStatuses
.map(([status, count]) => `${status}=${count}`)
.join(", ")}`,
};
}
const records = Array.isArray(report?.records) ? report.records : [];
if (records.length === 0) {
return { ok: false, reason: "missing selected scenario records" };
}
const groups = Array.isArray(report?.performance?.groups) ? report.performance.groups : [];
if (groups.length === 0) {
return { ok: false, reason: "missing performance groups" };
}
if (!groups.some((group) => hasSampledMetric(group, rssMetricIds))) {
return { ok: false, reason: "missing sampled RSS metric in performance groups" };
}
if (!groups.some((group) => hasSampledMetric(group, cpuMetricIds))) {
return { ok: false, reason: "missing sampled CPU metric in performance groups" };
}
return { ok: true };
return { ok: false, reason: `partial: ${partial.reason}; profiled: ${profiled.reason}` };
}
function readCliReportPath() {
@ -112,13 +552,13 @@ const invokedPath = process.argv[1] ? fs.realpathSync.native(path.resolve(proces
if (modulePath === invokedPath) {
try {
const reportPath = readCliReportPath();
const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
const result = evaluateToleratedPartialKovaReport(report);
const report = JSON.parse(fs.readFileSync(readCliReportPath(), "utf8"));
const result = evaluateToleratedKovaReport(report);
if (!result.ok) {
console.error(`Kova PARTIAL verdict is not tolerable: ${result.reason}`);
console.error(`Kova verdict is not tolerable: ${result.reason}`);
process.exit(1);
}
console.log(`Tolerated Kova verdict: ${result.classification}`);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);

File diff suppressed because it is too large Load diff

View file

@ -102,6 +102,9 @@ describe("OpenClaw performance workflow", () => {
'node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-gate.mjs" "$report_json"',
);
expect(runKova.run).not.toContain("report.summary?.statuses ?? {}");
expect(runKova.run).toContain(
"profiling-affected resource thresholds with no baseline regression",
);
});
it("installs local workspace packages beside the OCM root tarball", () => {