mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
fa86caf94f
* fix(release): keep protected tooling trusted after main moves * fix(release): cover protected tooling recovery paths * fix(release): honor live tooling contracts * fix(release): revalidate tooling at npm publish * fix(release): bind npm publishers to live tooling * fix(release): preserve trusted dispatch identity * fix(release): revalidate parent authorization * fix(release): bind ClawHub to release parent * docs(release): define frozen tooling identity * test(release): align ClawHub protected dispatch ref * fix(release): trust protected plugin npm preflight tooling * docs(release): scope protected writer guarantees * fix(release): keep protected tooling foundation npm-only * test(release): cover trusted npm preflight tooling
251 lines
8.0 KiB
TypeScript
251 lines
8.0 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
resolveOpenClawNpmResumeRun,
|
|
runOpenClawNpmResumeGh,
|
|
validateOpenClawNpmResumeRun,
|
|
} from "../../scripts/openclaw-npm-resume-run.mts";
|
|
import type { OpenClawNpmResumeValidationInput } from "../../scripts/openclaw-npm-resume-run.mts";
|
|
|
|
const SHA = "a".repeat(40);
|
|
const TAG_OBJECT_SHA = "b".repeat(40);
|
|
const BRANCH = `release-publish/${SHA.slice(0, 12)}-123`;
|
|
const URL = "https://github.com/openclaw/openclaw/actions/runs/456";
|
|
|
|
function fixture(
|
|
overrides: Partial<OpenClawNpmResumeValidationInput> = {},
|
|
): OpenClawNpmResumeValidationInput {
|
|
return {
|
|
canonicalWorkflowId: 101,
|
|
compareStatus: "identical",
|
|
jobs: [{ conclusion: "success", name: "validate_publish_request" }],
|
|
run: {
|
|
conclusion: "success",
|
|
event: "workflow_dispatch",
|
|
head_branch: BRANCH,
|
|
head_sha: SHA,
|
|
html_url: URL,
|
|
path: ".github/workflows/openclaw-npm-release.yml",
|
|
workflow_id: 101,
|
|
},
|
|
tag: {
|
|
object: { sha: SHA, type: "commit" },
|
|
verification: { verified: true },
|
|
},
|
|
tagRef: { object: { sha: TAG_OBJECT_SHA, type: "tag" } },
|
|
trustedWorkflowFullRef: `refs/tags/${BRANCH}`,
|
|
trustedWorkflowRef: BRANCH,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("openclaw npm resume run identity", () => {
|
|
it("bounds each GitHub lookup", () => {
|
|
const execFileSyncImpl = vi.fn(() => "result");
|
|
|
|
expect(
|
|
runOpenClawNpmResumeGh(["api", "repos/openclaw/openclaw/actions/runs/456"], {
|
|
execFileSyncImpl,
|
|
}),
|
|
).toBe("result");
|
|
expect(execFileSyncImpl).toHaveBeenCalledWith(
|
|
"gh",
|
|
["api", "repos/openclaw/openclaw/actions/runs/456"],
|
|
{
|
|
encoding: "utf8",
|
|
killSignal: "SIGKILL",
|
|
maxBuffer: 32 * 1024 * 1024,
|
|
timeout: 60_000,
|
|
},
|
|
);
|
|
});
|
|
|
|
it("propagates GitHub lookup timeouts", () => {
|
|
const timeoutError = Object.assign(new Error("spawnSync gh ETIMEDOUT"), {
|
|
code: "ETIMEDOUT",
|
|
});
|
|
|
|
expect(() =>
|
|
runOpenClawNpmResumeGh(["api", "repos/openclaw/openclaw/actions/runs/456"], {
|
|
execFileSyncImpl: () => {
|
|
throw timeoutError;
|
|
},
|
|
}),
|
|
).toThrow(timeoutError);
|
|
});
|
|
|
|
it("accepts a successful run bound to a signed main-reachable tooling tag", () => {
|
|
expect(validateOpenClawNpmResumeRun(fixture())).toEqual({
|
|
tagObjectSha: TAG_OBJECT_SHA,
|
|
url: URL,
|
|
workflowRef: `refs/tags/${BRANCH}`,
|
|
workflowSha: SHA,
|
|
});
|
|
});
|
|
|
|
it("accepts a successful run bound to the exact lightweight protected tooling tag", () => {
|
|
expect(
|
|
validateOpenClawNpmResumeRun(
|
|
fixture({
|
|
compareStatus: undefined,
|
|
tag: {},
|
|
tagRef: { object: { sha: SHA, type: "commit" } },
|
|
}),
|
|
),
|
|
).toEqual({
|
|
tagObjectSha: SHA,
|
|
url: URL,
|
|
workflowRef: `refs/tags/${BRANCH}`,
|
|
workflowSha: SHA,
|
|
});
|
|
});
|
|
|
|
it("accepts the canonical path shape returned by the Actions workflow run API", () => {
|
|
expect(
|
|
validateOpenClawNpmResumeRun(
|
|
fixture({
|
|
run: {
|
|
conclusion: "success",
|
|
event: "workflow_dispatch",
|
|
head_branch: BRANCH,
|
|
head_sha: SHA,
|
|
html_url: URL,
|
|
path: ".github/workflows/openclaw-npm-release.yml",
|
|
workflow_id: 101,
|
|
},
|
|
}),
|
|
),
|
|
).toMatchObject({
|
|
workflowRef: `refs/tags/${BRANCH}`,
|
|
workflowSha: SHA,
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["branch", { run: { ...fixture().run, head_branch: "main" } }, "untrusted workflow identity"],
|
|
["workflow", { run: { ...fixture().run, workflow_id: 999 } }, "untrusted workflow identity"],
|
|
["event", { run: { ...fixture().run, event: "push" } }, "untrusted workflow identity"],
|
|
[
|
|
"conclusion",
|
|
{ run: { ...fixture().run, conclusion: "failure" } },
|
|
"untrusted workflow identity",
|
|
],
|
|
[
|
|
"path",
|
|
{ run: { ...fixture().run, path: ".github/workflows/ci.yml" } },
|
|
"untrusted workflow identity",
|
|
],
|
|
[
|
|
"same-name branch full ref",
|
|
{ trustedWorkflowFullRef: `refs/heads/${BRANCH}` },
|
|
"untrusted workflow ref",
|
|
],
|
|
[
|
|
"mismatched supplied ref",
|
|
{ trustedWorkflowRef: `release-publish/${SHA.slice(0, 12)}-124` },
|
|
"untrusted workflow ref",
|
|
],
|
|
[
|
|
"tag kind",
|
|
{ tagRef: { object: { sha: TAG_OBJECT_SHA, type: "tree" } } },
|
|
"not a protected tag",
|
|
],
|
|
[
|
|
"moved lightweight tag",
|
|
{
|
|
compareStatus: undefined,
|
|
tag: {},
|
|
tagRef: { object: { sha: "c".repeat(40), type: "commit" } },
|
|
},
|
|
"moved after dispatch",
|
|
],
|
|
[
|
|
"tag target",
|
|
{ tag: { ...fixture().tag, object: { sha: "c".repeat(40), type: "commit" } } },
|
|
"not bound to a real",
|
|
],
|
|
[
|
|
"signature",
|
|
{ tag: { ...fixture().tag, verification: { verified: false } } },
|
|
"not bound to a real",
|
|
],
|
|
["main ancestry", { compareStatus: "diverged" }, "not bound to a real"],
|
|
[
|
|
"approval",
|
|
{ jobs: [{ conclusion: "failure", name: "validate_publish_request" }] },
|
|
"lacks successful parent release approval",
|
|
],
|
|
])("rejects an untrusted %s", (_label, overrides, message) => {
|
|
expect(() => validateOpenClawNpmResumeRun(fixture(overrides))).toThrow(message);
|
|
});
|
|
|
|
it("loads the exact run, workflow, signed tag, ancestry, and approval job", () => {
|
|
const responses = new Map<string, unknown>([
|
|
[`api repos/openclaw/openclaw/actions/runs/456 --method GET`, fixture().run],
|
|
[
|
|
`api repos/openclaw/openclaw/actions/workflows/openclaw-npm-release.yml --method GET`,
|
|
{ id: 101 },
|
|
],
|
|
[`api repos/openclaw/openclaw/git/ref/tags/${BRANCH} --method GET`, fixture().tagRef],
|
|
[`api repos/openclaw/openclaw/git/tags/${TAG_OBJECT_SHA} --method GET`, fixture().tag],
|
|
[`api repos/openclaw/openclaw/compare/${SHA}...main --method GET`, { status: "identical" }],
|
|
[`run view 456 --repo openclaw/openclaw --json jobs --jq .jobs`, fixture().jobs],
|
|
]);
|
|
const runGh = vi.fn((args: string[]) => {
|
|
const response = responses.get(args.join(" "));
|
|
if (!response) {
|
|
throw new Error(`Unexpected gh invocation: ${args.join(" ")}`);
|
|
}
|
|
return JSON.stringify(response);
|
|
});
|
|
|
|
expect(
|
|
resolveOpenClawNpmResumeRun({
|
|
repo: "openclaw/openclaw",
|
|
runGh,
|
|
runId: "456",
|
|
trustedWorkflowFullRef: `refs/tags/${BRANCH}`,
|
|
trustedWorkflowRef: BRANCH,
|
|
}),
|
|
).toMatchObject({ workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA });
|
|
expect(runGh).toHaveBeenCalledTimes(6);
|
|
});
|
|
|
|
it("loads a lightweight protected tag without requiring tag metadata or main ancestry", () => {
|
|
const lightweight = fixture({
|
|
compareStatus: undefined,
|
|
tag: {},
|
|
tagRef: { object: { sha: SHA, type: "commit" } },
|
|
});
|
|
const responses = new Map<string, unknown>([
|
|
[`api repos/openclaw/openclaw/actions/runs/456 --method GET`, lightweight.run],
|
|
[
|
|
`api repos/openclaw/openclaw/actions/workflows/openclaw-npm-release.yml --method GET`,
|
|
{ id: 101 },
|
|
],
|
|
[`api repos/openclaw/openclaw/git/ref/tags/${BRANCH} --method GET`, lightweight.tagRef],
|
|
[`run view 456 --repo openclaw/openclaw --json jobs --jq .jobs`, lightweight.jobs],
|
|
]);
|
|
const runGh = vi.fn((args: string[]) => {
|
|
const response = responses.get(args.join(" "));
|
|
if (!response) {
|
|
throw new Error(`Unexpected gh invocation: ${args.join(" ")}`);
|
|
}
|
|
return JSON.stringify(response);
|
|
});
|
|
|
|
expect(
|
|
resolveOpenClawNpmResumeRun({
|
|
repo: "openclaw/openclaw",
|
|
runGh,
|
|
runId: "456",
|
|
trustedWorkflowFullRef: `refs/tags/${BRANCH}`,
|
|
trustedWorkflowRef: BRANCH,
|
|
}),
|
|
).toMatchObject({ workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA });
|
|
expect(runGh).toHaveBeenCalledTimes(4);
|
|
expect(runGh.mock.calls.flatMap(([args]) => args)).not.toContain(
|
|
`repos/openclaw/openclaw/compare/${SHA}...main`,
|
|
);
|
|
});
|
|
});
|