openclaw/scripts/mantis/request-workflow-admission.mjs
Martin Cleary a960844a9c
feat(qa): run isolated behavioral proof for inline reviews (#138953)
* 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>
2026-09-07 14:09:37 +01:00

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,
});
}