Files
openclaw/scripts/mantis/build-web-ui-chat-evidence.mjs
brokemac79 037412ec7d ci(mantis): add web UI chat proof lane (#100472)
* ci(mantis): add web ui chat proof lane

* ci(mantis): tighten web ui proof candidate parsing

* ci: tighten Mantis Web UI proof lane

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-07 10:54:35 +01:00

175 lines
5.4 KiB
JavaScript

#!/usr/bin/env node
// Builds a Mantis evidence manifest from Control UI web chat proof artifacts.
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 1) {
const key = argv[index];
if (!key.startsWith("--")) {
throw new Error(`Unexpected argument: ${key}`);
}
const name = key.slice(2).replaceAll("-", "_");
const value = argv[index + 1];
if (!value || value.startsWith("--")) {
throw new Error(`Missing value for ${key}`);
}
args[name] = value;
index += 1;
}
return args;
}
function normalizeStatus(value) {
const normalized = String(value ?? "")
.trim()
.toLowerCase();
if (normalized === "pass") {
return "pass";
}
if (normalized === "fail") {
return "fail";
}
throw new Error(`Unsupported web UI chat proof status: ${value}`);
}
function artifactEntry({ inline = false, kind, label, path: artifactPath, required, targetPath }) {
return {
kind,
lane: "candidate",
label,
path: artifactPath,
targetPath,
required,
...(inline ? { alt: label, inline: true, width: 900 } : {}),
};
}
export function buildWebUiChatEvidenceManifest({ candidateRef, candidateSha, status }) {
const passed = status === "pass";
return {
schemaVersion: 1,
id: "web-ui-chat-proof",
title: "Mantis Web UI Chat Proof",
summary:
"Mantis ran the OpenClaw Control UI chat proof against the candidate ref, sent a message through the mocked Gateway, rendered the final reply in the browser, and captured browser artifacts for review.",
scenario: "web-ui-chat-proof",
comparison: {
candidate: {
...(candidateSha ? { sha: candidateSha } : {}),
...(candidateRef ? { ref: candidateRef } : {}),
expected: "Control UI chat sends through the Gateway and renders the final reply",
status,
fixed: passed,
},
pass: passed,
},
artifacts: [
artifactEntry({
inline: true,
kind: "desktopScreenshot",
label: "Control UI web chat proof",
path: "web-ui-chat.png",
required: passed,
targetPath: "web-ui-chat.png",
}),
artifactEntry({
kind: "fullVideo",
label: "Control UI web chat recording",
path: "web-ui-chat.webm",
required: false,
targetPath: "web-ui-chat.webm",
}),
artifactEntry({
kind: "metadata",
label: "Control UI web chat proof metadata",
path: "web-ui-chat-proof.json",
required: passed,
targetPath: "web-ui-chat-proof.json",
}),
artifactEntry({
kind: "metadata",
label: "Control UI web chat Vitest log",
path: "vitest.log",
required: false,
targetPath: "vitest.log",
}),
{
kind: "report",
lane: "run",
label: "Mantis web UI chat report",
path: "mantis-report.md",
targetPath: "mantis-report.md",
},
],
};
}
function renderReport({ candidateRef, candidateSha, outputDir, status }) {
const artifactStatus = (artifactPath) =>
existsSync(path.join(outputDir, artifactPath)) ? "present" : "missing";
return [
"# Mantis Web UI Chat Proof",
"",
`Status: ${status}`,
`Candidate ref: ${candidateRef || "unspecified"}`,
`Candidate SHA: ${candidateSha || "unspecified"}`,
"",
"## Scenario",
"",
"OpenClaw Control UI chat was loaded in a browser with the mocked Gateway harness. The proof sends a chat message through the GUI, verifies the `chat.send` request, emits a final Gateway reply, and waits for the reply to render in the web chat thread.",
"",
"## Artifacts",
"",
`- Screenshot: \`web-ui-chat.png\` (${artifactStatus("web-ui-chat.png")})`,
`- Recording: \`web-ui-chat.webm\` (${artifactStatus("web-ui-chat.webm")})`,
`- Proof metadata: \`web-ui-chat-proof.json\` (${artifactStatus("web-ui-chat-proof.json")})`,
`- Vitest log: \`vitest.log\` (${artifactStatus("vitest.log")})`,
"",
].join("\n");
}
export function writeWebUiChatEvidence(rawArgs = process.argv.slice(2)) {
const args = parseArgs(rawArgs);
if (!args.output_dir) {
throw new Error("Missing --output-dir.");
}
if (!args.status) {
throw new Error("Missing --status.");
}
const outputDir = path.resolve(args.output_dir);
mkdirSync(outputDir, { recursive: true });
const status = normalizeStatus(args.status);
const manifest = buildWebUiChatEvidenceManifest({
candidateRef: args.candidate_ref,
candidateSha: args.candidate_sha,
status,
});
const reportPath = path.join(outputDir, "mantis-report.md");
writeFileSync(
reportPath,
renderReport({
candidateRef: args.candidate_ref,
candidateSha: args.candidate_sha,
outputDir,
status,
}),
"utf8",
);
const manifestPath = path.join(outputDir, "mantis-evidence.json");
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
return { manifest, manifestPath, reportPath };
}
const executedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
if (executedPath === fileURLToPath(import.meta.url)) {
try {
writeWebUiChatEvidence();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
}