mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
fix(ci): harden full release validation
This commit is contained in:
@@ -71,7 +71,8 @@ env:
|
||||
OCM_LINUX_X64_SHA256: 57530199d21eb5bfa29695749928b40fd2869484c7edff69b7c65bfc84f2f1aa
|
||||
KOVA_REPOSITORY: openclaw/Kova
|
||||
PERFORMANCE_MODEL_ID: gpt-5.6-luna
|
||||
KOVA_SCENARIO_TIMEOUT_MS: "300000"
|
||||
# Release matrices cold-build the candidate runtime before measurement.
|
||||
KOVA_SCENARIO_TIMEOUT_MS: ${{ inputs.profile == 'release' && '900000' || '300000' }}
|
||||
|
||||
jobs:
|
||||
resolve_target:
|
||||
|
||||
@@ -16,28 +16,6 @@ const DEFAULT_INPUTS = {
|
||||
rerun_group: "all",
|
||||
reuse_evidence: "true",
|
||||
};
|
||||
const WORKFLOW_RUN_CHECK_SUITE_QUERY = `
|
||||
query($owner: String!, $repo: String!, $oid: GitObjectID!, $after: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
object(oid: $oid) {
|
||||
... on Commit {
|
||||
checkSuites(first: 100, after: $after) {
|
||||
nodes {
|
||||
status
|
||||
conclusion
|
||||
workflowRun {
|
||||
url
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
function usage() {
|
||||
console.error(`Usage: node scripts/full-release-validation-at-sha.mjs [--sha <target-sha>] [--target-ref <canonical-release-branch-or-tag>] [--workflow-sha <trusted-main-ref>] [--keep-branch] [--dry-run] [-- -f key=value ...]
|
||||
@@ -282,50 +260,19 @@ function findLatestRunId(branch, sha) {
|
||||
return match?.databaseId ? String(match.databaseId) : "";
|
||||
}
|
||||
|
||||
export function selectWorkflowRunCheckSuite(nodes, parentRunId) {
|
||||
function readWorkflowRun(parentRunId, workflowSha) {
|
||||
if (!/^[1-9][0-9]*$/u.test(String(parentRunId))) {
|
||||
throw new Error("parent run ID must be a positive decimal");
|
||||
}
|
||||
const expectedUrl = `https://github.com/openclaw/openclaw/actions/runs/${parentRunId}`;
|
||||
return nodes.find((node) => node?.workflowRun?.url === expectedUrl);
|
||||
}
|
||||
|
||||
function readWorkflowRunCheckSuite(parentRunId, workflowSha) {
|
||||
let after;
|
||||
for (let page = 0; page < 20; page += 1) {
|
||||
const queryArgs = [
|
||||
"api",
|
||||
"graphql",
|
||||
"-F",
|
||||
"owner=openclaw",
|
||||
"-F",
|
||||
"repo=openclaw",
|
||||
"-F",
|
||||
`oid=${workflowSha}`,
|
||||
"-f",
|
||||
`query=${WORKFLOW_RUN_CHECK_SUITE_QUERY}`,
|
||||
];
|
||||
if (after) {
|
||||
queryArgs.push("-F", `after=${after}`);
|
||||
}
|
||||
const response = JSON.parse(run("gh", queryArgs));
|
||||
const checkSuites = response?.data?.repository?.object?.checkSuites;
|
||||
if (!checkSuites || !Array.isArray(checkSuites.nodes)) {
|
||||
throw new Error("GraphQL response did not include commit check suites");
|
||||
}
|
||||
const match = selectWorkflowRunCheckSuite(checkSuites.nodes, parentRunId);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
if (!checkSuites.pageInfo?.hasNextPage) {
|
||||
return undefined;
|
||||
}
|
||||
after = checkSuites.pageInfo.endCursor;
|
||||
if (!after) {
|
||||
throw new Error("GraphQL check-suite pagination omitted its end cursor");
|
||||
}
|
||||
const workflowRun = JSON.parse(
|
||||
run("gh", ["api", `repos/openclaw/openclaw/actions/runs/${parentRunId}`]),
|
||||
);
|
||||
if (workflowRun.head_sha !== workflowSha) {
|
||||
throw new Error(
|
||||
`Full Release Validation run ${parentRunId} head ${String(workflowRun.head_sha)} does not match trusted workflow SHA ${workflowSha}`,
|
||||
);
|
||||
}
|
||||
throw new Error("Full Release Validation check suite was not found within 20 pages");
|
||||
return workflowRun;
|
||||
}
|
||||
|
||||
function waitForWorkflowRun(parentRunId, workflowSha) {
|
||||
@@ -334,7 +281,7 @@ function waitForWorkflowRun(parentRunId, workflowSha) {
|
||||
for (let attempt = 0; attempt < 480; attempt += 1) {
|
||||
let suite;
|
||||
try {
|
||||
suite = readWorkflowRunCheckSuite(parentRunId, workflowSha);
|
||||
suite = readWorkflowRun(parentRunId, workflowSha);
|
||||
consecutiveErrors = 0;
|
||||
} catch (error) {
|
||||
consecutiveErrors += 1;
|
||||
@@ -345,15 +292,13 @@ function waitForWorkflowRun(parentRunId, workflowSha) {
|
||||
console.warn(`Parent run status query failed; retrying: ${message}`);
|
||||
}
|
||||
|
||||
const summary = suite
|
||||
? `${String(suite.status).toLowerCase()}/${String(suite.conclusion ?? "pending").toLowerCase()}`
|
||||
: "awaiting-check-suite";
|
||||
const summary = `${String(suite?.status ?? "pending").toLowerCase()}/${String(suite?.conclusion ?? "pending").toLowerCase()}`;
|
||||
if (summary !== lastSummary) {
|
||||
console.log(`Parent run status: ${summary}`);
|
||||
lastSummary = summary;
|
||||
}
|
||||
if (suite?.status === "COMPLETED") {
|
||||
if (suite.conclusion === "SUCCESS") {
|
||||
if (suite?.status === "completed") {
|
||||
if (suite.conclusion === "success") {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
releaseEvidenceVerificationArgs,
|
||||
releaseEvidenceVerifierPath,
|
||||
resolveRemoteTargetRefSha,
|
||||
selectWorkflowRunCheckSuite,
|
||||
} from "../../scripts/full-release-validation-at-sha.mjs";
|
||||
|
||||
describe("full-release-validation-at-sha", () => {
|
||||
@@ -148,26 +147,11 @@ describe("full-release-validation-at-sha", () => {
|
||||
expect(() => releaseEvidenceVerificationArgs("")).toThrow("positive decimal");
|
||||
});
|
||||
|
||||
it("selects the exact workflow run through GraphQL check-suite metadata", () => {
|
||||
const nodes = [
|
||||
{
|
||||
status: "COMPLETED",
|
||||
conclusion: "SUCCESS",
|
||||
workflowRun: { url: "https://github.com/openclaw/openclaw/actions/runs/122" },
|
||||
},
|
||||
{
|
||||
status: "IN_PROGRESS",
|
||||
conclusion: null,
|
||||
workflowRun: { url: "https://github.com/openclaw/openclaw/actions/runs/123" },
|
||||
},
|
||||
];
|
||||
|
||||
expect(selectWorkflowRunCheckSuite(nodes, "123")).toEqual(nodes[1]);
|
||||
expect(selectWorkflowRunCheckSuite(nodes, "999")).toBeUndefined();
|
||||
expect(() => selectWorkflowRunCheckSuite(nodes, "")).toThrow("positive decimal");
|
||||
|
||||
it("polls the exact workflow run without GraphQL quota use", () => {
|
||||
const source = readFileSync("scripts/full-release-validation-at-sha.mjs", "utf8");
|
||||
expect(source).toContain("checkSuites(first: 100, after: $after)");
|
||||
expect(source).toContain("actions/runs/${parentRunId}");
|
||||
expect(source).toContain("workflowRun.head_sha !== workflowSha");
|
||||
expect(source).not.toContain('"graphql"');
|
||||
expect(source).not.toContain('["run", "watch"');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user