mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(release): require terminal frozen QA evidence
This commit is contained in:
@@ -168,6 +168,21 @@ function requireCanonicalRuntimePair(runtimePair: unknown) {
|
||||
);
|
||||
}
|
||||
|
||||
function matchesFrozenRuntimePairManifest(
|
||||
options: { targetSha?: string; lane?: string },
|
||||
scenarioIds: unknown[],
|
||||
skippedScenarioIds: unknown[],
|
||||
) {
|
||||
const manifest = FROZEN_RUNTIME_PAIR_MANIFESTS.get(`${options.targetSha}:${options.lane}`);
|
||||
return (
|
||||
manifest !== undefined &&
|
||||
manifest.scenarioIds.length === scenarioIds.length &&
|
||||
manifest.scenarioIds.every((scenarioId, index) => scenarioId === scenarioIds[index]) &&
|
||||
manifest.gapScenarioIds.length === skippedScenarioIds.length &&
|
||||
manifest.gapScenarioIds.every((scenarioId, index) => scenarioId === skippedScenarioIds[index])
|
||||
);
|
||||
}
|
||||
|
||||
export function validateQaRuntimePairSummary(
|
||||
summary: unknown,
|
||||
options: { requireExplicitGap?: boolean; targetSha?: string; lane?: string } = {},
|
||||
@@ -175,11 +190,7 @@ export function validateQaRuntimePairSummary(
|
||||
if (!isRecord(summary) || !isRecord(summary.run) || !Array.isArray(summary.scenarios)) {
|
||||
throw new Error("runtime-pair summary is missing run or scenario evidence");
|
||||
}
|
||||
const completedLegacyRun =
|
||||
summary.run.status === undefined &&
|
||||
typeof summary.run.finishedAt === "string" &&
|
||||
Number.isFinite(Date.parse(summary.run.finishedAt));
|
||||
if (summary.run.status !== "completed" && !completedLegacyRun) {
|
||||
if (summary.run.status !== "completed" && summary.run.status !== undefined) {
|
||||
throw new Error("runtime-pair summary is not completed");
|
||||
}
|
||||
if (!requireCanonicalRuntimePair(summary.run.runtimePair)) {
|
||||
@@ -218,6 +229,13 @@ export function validateQaRuntimePairSummary(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
run.status === undefined &&
|
||||
!matchesFrozenRuntimePairManifest(options, scenarioIds, skippedScenarioIds)
|
||||
) {
|
||||
throw new Error("runtime-pair summary is not completed");
|
||||
}
|
||||
|
||||
const expectedCounts = {
|
||||
total: summary.scenarios.length,
|
||||
passed,
|
||||
@@ -239,14 +257,7 @@ export function validateQaRuntimePairSummary(
|
||||
throw new Error("nonzero candidate suite exit requires an explicit Codex-native harness gap");
|
||||
}
|
||||
if (options.requireExplicitGap === true) {
|
||||
const manifest = FROZEN_RUNTIME_PAIR_MANIFESTS.get(`${options.targetSha}:${options.lane}`);
|
||||
if (
|
||||
!manifest ||
|
||||
manifest.scenarioIds.length !== scenarioIds.length ||
|
||||
manifest.scenarioIds.some((scenarioId, index) => scenarioId !== scenarioIds[index]) ||
|
||||
manifest.gapScenarioIds.length !== skippedScenarioIds.length ||
|
||||
manifest.gapScenarioIds.some((scenarioId, index) => scenarioId !== skippedScenarioIds[index])
|
||||
) {
|
||||
if (!matchesFrozenRuntimePairManifest(options, scenarioIds, skippedScenarioIds)) {
|
||||
throw new Error("nonzero candidate exit is not covered by a trusted frozen-lane manifest");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,16 +135,20 @@ describe("frozen QA runtime-pair summary validation", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts a completed legacy summary that used its finish timestamp", () => {
|
||||
const fixture = summary([scenario({ name: "legacy terminal", status: "pass" })]);
|
||||
it("accepts a statusless summary only for an exact frozen terminal manifest", () => {
|
||||
const fixture = frozenCoreSummary();
|
||||
Reflect.deleteProperty(fixture.run, "status");
|
||||
Object.assign(fixture.run, { finishedAt: "2026-08-21T03:34:17.434Z" });
|
||||
|
||||
expect(validateQaRuntimePairSummary(fixture)).toEqual({
|
||||
total: 1,
|
||||
passed: 1,
|
||||
expect(
|
||||
validateQaRuntimePairSummary(fixture, {
|
||||
targetSha: "311047822ecdde24e824d839ab105ef08f17be00",
|
||||
lane: "core",
|
||||
}),
|
||||
).toEqual({
|
||||
total: 27,
|
||||
passed: 19,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
skipped: 8,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,6 +161,18 @@ describe("frozen QA runtime-pair summary validation", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a partial statusless artifact for a frozen candidate", () => {
|
||||
const fixture = summary([scenario({ name: frozenCoreScenarioIds[0], status: "pass" })]);
|
||||
Reflect.deleteProperty(fixture.run, "status");
|
||||
|
||||
expect(() =>
|
||||
validateQaRuntimePairSummary(fixture, {
|
||||
targetSha: "311047822ecdde24e824d839ab105ef08f17be00",
|
||||
lane: "core",
|
||||
}),
|
||||
).toThrow("runtime-pair summary is not completed");
|
||||
});
|
||||
|
||||
it("accepts only passing scenarios and explicit one-sided Codex-native gaps", () => {
|
||||
const fixture = summary([
|
||||
scenario({ name: "passing", status: "pass" }),
|
||||
|
||||
Reference in New Issue
Block a user