mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* feat(telegram): add isolated Test Server proof workflow Add maintainer-only exact-head admission, durable at-most-once QA lease consumption, isolated candidate execution, and normalized trusted Telegram Test Server observations. Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com> * feat(proof): bind named Web UI and canonical Telegram QA evidence Reuse the existing formatting QA recipe, preserve isolated exact-candidate execution and produce the consumer request-bound receipt. Keep named smoke scenarios distinct and protect stationary harness ancestry. Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com> * fix(proof): complete isolated QA execution and bounded failure capture Reuse canonical ephemeral device pairing and QA RPC scopes, preserve strict startup probes and recorder locks, and retain bounded wrong-text attempts without Telegram delivery. Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com> * fix(mantis): revoke proof forwarding and enforce lease roles * fix(mantis): close proof producer CI gates * test(mantis): consolidate related proof suites within CI budget * test(mantis): preserve fast QA ownership when grouping integration cases * fix(mantis): accept exact branch-qualified workflow paths * fix(mantis): align live admission workflow path checks * feat(mantis): collect selected proof inside the originating review * fix(mantis): bound proof storage and preserve failure evidence Reuse verified storage across request-bound candidates, reserve backing capacity, retain sanitized rejection evidence, and repair cleanup and observation finalization. Scoped checks and dirty review pass; full Gateway and sandboxed browser runtime proof remain required before publication. * fix(mantis): retain bridge identity before startup * fix(mantis): keep candidate config readable under private umask * fix(qa): use verified rootless networking and join candidate shutdown * fix(qa): repair proof tooling checks and deterministic recorder fixture --------- Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
83 lines
2.8 KiB
JavaScript
83 lines
2.8 KiB
JavaScript
import { pathToFileURL } from "node:url";
|
|
|
|
// Consumer configuration still owns the approved ref/SHA pair. This producer
|
|
// guard lets that ref stay stationary without trusting an arbitrary PR branch.
|
|
export async function assertRequestWorkflowRef({ repository, ref, sha, token, fetchImpl = fetch }) {
|
|
if (
|
|
repository !== "openclaw/openclaw" ||
|
|
!ref?.startsWith("refs/heads/") ||
|
|
!/^[a-f0-9]{40}$/.test(sha ?? "") ||
|
|
!token
|
|
) {
|
|
throw new Error("Malformed trusted workflow identity");
|
|
}
|
|
const branch = ref.slice("refs/heads/".length);
|
|
if (!branch || branch.includes("..") || /[\s~^:?*[\\]/.test(branch)) {
|
|
throw new Error("Invalid workflow branch");
|
|
}
|
|
const read = async (route) => {
|
|
const response = await fetchImpl(`https://api.github.com/repos/${repository}/${route}`, {
|
|
headers: {
|
|
authorization: `Bearer ${token}`,
|
|
accept: "application/vnd.github+json",
|
|
"x-github-api-version": "2022-11-28",
|
|
},
|
|
redirect: "error",
|
|
signal: AbortSignal.timeout(20_000),
|
|
});
|
|
if (!response.ok || !response.body) {
|
|
throw new Error("Workflow trust read failed");
|
|
}
|
|
const chunks = [];
|
|
let size = 0;
|
|
for await (const chunk of response.body) {
|
|
size += chunk.length;
|
|
if (size > 1024 * 1024) {
|
|
throw new Error("Workflow trust response is oversized");
|
|
}
|
|
chunks.push(Buffer.from(chunk));
|
|
}
|
|
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
};
|
|
const readBranch = async (name) => {
|
|
const value = await read(`branches/${encodeURIComponent(name)}`);
|
|
if (
|
|
value.name !== name ||
|
|
value.protected !== true ||
|
|
!/^[a-f0-9]{40}$/.test(value.commit?.sha ?? "")
|
|
) {
|
|
throw new Error("Workflow branch protection or identity is unverified");
|
|
}
|
|
return value.commit.sha;
|
|
};
|
|
const pinned = await readBranch(branch);
|
|
const main = branch === "main" ? pinned : await readBranch("main");
|
|
if (pinned !== sha) {
|
|
throw new Error("Workflow branch moved from the executed SHA");
|
|
}
|
|
if (main !== sha) {
|
|
const comparison = await read(`compare/${sha}...${main}?per_page=1`);
|
|
if (
|
|
comparison.status !== "ahead" ||
|
|
comparison.merge_base_commit?.sha !== sha ||
|
|
comparison.base_commit?.sha !== sha
|
|
) {
|
|
throw new Error("Workflow SHA is not verified main ancestry");
|
|
}
|
|
}
|
|
if (
|
|
(await readBranch(branch)) !== pinned ||
|
|
(branch !== "main" && (await readBranch("main")) !== main)
|
|
) {
|
|
throw new Error("Workflow trust changed during admission");
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
await assertRequestWorkflowRef({
|
|
repository: process.env.GITHUB_REPOSITORY,
|
|
ref: process.env.GITHUB_REF,
|
|
sha: process.env.GITHUB_WORKFLOW_SHA,
|
|
token: process.env.GH_TOKEN,
|
|
});
|
|
}
|