From f4b3a4841f22b72afcbcae8909a78adf06c05b7e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 10 Jul 2026 17:31:42 -0700 Subject: [PATCH] fix(release): accept Kova collector-only phases (#104008) --- scripts/lib/kova-report-gate.mjs | 37 +++++++++++++++++++-- test/scripts/kova-report-gate.test.ts | 47 +++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/scripts/lib/kova-report-gate.mjs b/scripts/lib/kova-report-gate.mjs index 742551c48d0a..5eb7ff018e8a 100644 --- a/scripts/lib/kova-report-gate.mjs +++ b/scripts/lib/kova-report-gate.mjs @@ -96,6 +96,23 @@ function validateCommandResult(value, label) { check(result.status === 0 && result.timedOut === false, `${label} failed`); } +function validateCollectorOnlyPhase(phase) { + check( + phase.driverKind === "none" && phase.collectionIntent === "post-ready-health", + "commandless phase was not a collector-only phase", + ); + const evidence = array(phase.evidence, "collector-only phase evidence"); + check( + evidence.length > 0 && + evidence.every((entry) => typeof entry === "string" && entry.trim().length > 0), + "collector-only phase evidence was invalid", + ); + check( + object(phase.metrics, "collector-only phase metrics").schemaVersion === "kova.envMetrics.v1", + "collector-only phase metrics schema was invalid", + ); +} + function alreadyAbsent(result, noun) { check( Number.isSafeInteger(result.status) && result.status !== 0, @@ -128,9 +145,23 @@ function validateRecord(recordValue) { 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}`)); + const phase = object(phaseValue, "phase"); + const commands = array(phase.commands, "phase commands"); + const results = array(phase.results, "phase results"); + check(commands.length === results.length, "phase command/result counts did not match"); + if (commands.length === 0) { + validateCollectorOnlyPhase(phase); + continue; + } + results.forEach((resultValue, index) => { + const result = object(resultValue, `phase result ${index}`); + check( + text(result.command, `phase result ${index} command`) === + text(commands[index], `phase command ${index}`), + `phase command ${index} did not match its result`, + ); + validateCommandResult(result, `phase result ${index}`); + }); } validateCleanup( record.cleanup, diff --git a/test/scripts/kova-report-gate.test.ts b/test/scripts/kova-report-gate.test.ts index b626be5f7c49..5949dc288a69 100644 --- a/test/scripts/kova-report-gate.test.ts +++ b/test/scripts/kova-report-gate.test.ts @@ -206,7 +206,13 @@ function partialReport(): JsonObject { cpuPercentMax: 80, peakRssMb: 650, }, - phases: [{ id: "agent-turn", results: [commandResult()] }], + phases: [ + { + commands: [commandResult().command], + id: "agent-turn", + results: [commandResult()], + }, + ], profiling: normalProfiling(), scenario: SCENARIO, state: { id: STATE }, @@ -300,7 +306,13 @@ function profiledResourceReport(): JsonObject { "agent-process": { maxCpuPercent: 156.2, peakRssMb: 923.7 }, }, }, - phases: [{ id: "agent-turn", results: [commandResult()] }], + phases: [ + { + commands: [commandResult().command], + id: "agent-turn", + results: [commandResult()], + }, + ], profiling: deepProfiling(), scenario: SCENARIO, state: { id: STATE }, @@ -417,6 +429,21 @@ describe("scripts/lib/kova-report-gate.mjs", () => { }); }); + it("accepts a declared collector-only phase without commands", () => { + const report = partialReport(); + setAt(report, ["records", 0, "phases", 0], { + collectionIntent: "post-ready-health", + commands: [], + driverKind: "none", + evidence: ["startup logs"], + id: "logs", + metrics: { schemaVersion: "kova.envMetrics.v1" }, + results: [], + }); + + expect(evaluateToleratedPartialKovaReport(report)).toEqual({ ok: true }); + }); + it("accepts an exact deep-profile resource-only rejection", () => { expect(evaluateToleratedProfiledKovaReport(profiledResourceReport())).toEqual({ ok: true, @@ -549,6 +576,15 @@ describe("scripts/lib/kova-report-gate.mjs", () => { "rejects timed-out phase commands", (report) => setAt(report, ["records", 0, "phases", 0, "results", 0, "timedOut"], true), ], + [ + "rejects phase command/result count drift", + (report) => setAt(report, ["records", 0, "phases", 0, "results"], []), + ], + [ + "rejects phase command/result identity drift", + (report) => + setAt(report, ["records", 0, "phases", 0, "results", 0, "command"], "different command"), + ], [ "rejects retained record cleanup", (report) => setAt(report, ["records", 0, "cleanup"], "retained"), @@ -832,6 +868,13 @@ describe("scripts/lib/kova-report-gate.mjs", () => { "rejects PARTIAL phase failures", (report) => setAt(report, ["records", 0, "phases", 0, "results", 0, "status"], 1), ], + [ + "rejects unmarked commandless PARTIAL phases", + (report) => { + setAt(report, ["records", 0, "phases", 0, "commands"], []); + setAt(report, ["records", 0, "phases", 0, "results"], []); + }, + ], [ "rejects PARTIAL cleanup failures", (report) => setAt(report, ["records", 0, "cleanup"], "destroy-failed"),