Files
openclaw/scripts/mantis/telegram-visible-proof.mjs
Ayaan Zaidi b41d5293b2 refactor(mantis): give Codex open-ended Telegram proof control (#128197)
* refactor(mantis): replace Telegram proof compiler with frozen visible replay

* refactor(mantis): let Codex own Telegram proof scenarios

* fix(mantis): isolate proof publication

* fix(mantis): bind baseline cache to revision

* chore(mantis): remove stale scenario-designer wording

* fix(mantis): align readable worktrees with trusted proof

* fix(mantis): register proof collector tooling

* refactor(mantis): keep collector functions private

* fix(mantis): publish agent-selected Telegram proof

* fix(mantis): trim proof media to final turn

* fix(mantis): fence lanes before evidence collection

* fix(mantis): verify lane termination before unlock
2026-08-24 11:47:26 +05:30

297 lines
9.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { isRecord } from "../../packages/normalization-core/src/record-coerce.ts";
const OUTCOMES = new Set(["blocked", "fail", "pass"]);
const MEDIA = {
previewGifCropped: { extension: "gif", kind: "timeline" },
screenshot: { extension: "png", kind: "attachment" },
trimmedVideoCropped: { extension: "mp4", kind: "motionClip" },
};
function fail(message) {
throw new Error(message);
}
function readJson(file) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
function writeJson(file, value) {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
}
function requiredText(value, label, maximum = 4_000) {
if (typeof value !== "string" || !value.trim() || value.length > maximum) {
fail(`${label} must contain 1 to ${maximum} characters.`);
}
return value.trim();
}
function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 2) {
const key = argv[index];
const value = argv[index + 1];
if (!key?.startsWith("--") || value === undefined || value.startsWith("--")) {
fail("Invalid collect arguments.");
}
args[key.slice(2).replaceAll("-", "_")] = value;
}
return args;
}
function requiredArg(args, name) {
const value = args[name];
if (!value) {
fail(`Missing --${name.replaceAll("_", "-")}.`);
}
return value;
}
function sha256(file) {
return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
}
function copy(source, target) {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.copyFileSync(source, target);
}
function validateAgentJudgment(file) {
const judgment = readJson(file);
if (!isRecord(judgment) || judgment.schemaVersion !== 2) {
fail("agent-evidence.json must use schemaVersion 2.");
}
const comparison = judgment.comparison;
if (!isRecord(comparison) || !OUTCOMES.has(comparison.outcome)) {
fail("agent-evidence.json needs a pass, blocked, or fail outcome.");
}
for (const lane of ["baseline", "candidate"]) {
const value = comparison[lane];
if (!isRecord(value) || typeof value.expectationMet !== "boolean") {
fail(`agent-evidence.json comparison.${lane} needs expectationMet.`);
}
requiredText(value.expected, `comparison.${lane}.expected`, 1_000);
requiredText(value.detail, `comparison.${lane}.detail`, 2_000);
}
if (comparison.pass !== (comparison.outcome === "pass")) {
fail("agent-evidence.json pass must agree with outcome.");
}
if (
comparison.outcome === "pass" &&
(!comparison.baseline.expectationMet || !comparison.candidate.expectationMet)
) {
fail("A passing judgment requires both lane expectations to be met.");
}
requiredText(judgment.title, "title", 200);
requiredText(judgment.summary, "summary", 2_000);
requiredText(judgment.scenario, "scenario", 1_000);
requiredText(comparison.differential, "comparison.differential", 2_000);
return judgment;
}
function artifactRecord(record, lane, name, publishedRoot) {
if (!isRecord(record) || typeof record.file !== "string") {
fail(`${lane} facts are missing ${name}.`);
}
if (record.file !== path.basename(record.file)) {
fail(`${lane} ${name} has an invalid filename.`);
}
const source = path.join(publishedRoot, lane, record.file);
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) {
fail(`${lane} ${name} is missing.`);
}
if (fs.statSync(source).size !== record.bytes || sha256(source) !== record.sha256) {
fail(`${lane} ${name} failed integrity validation.`);
}
return source;
}
function loadLane({ factsFile, expectedSha, lane, outputDir, publishedRoot }) {
const facts = readJson(factsFile);
if (!isRecord(facts) || facts.schemaVersion !== 2 || facts.lane !== lane) {
fail(`${lane} lane facts are invalid.`);
}
if (
!isRecord(facts.sutAttestation) ||
facts.sutAttestation.lane !== lane ||
facts.sutAttestation.sha !== expectedSha
) {
fail(`${lane} SUT attestation does not match ${expectedSha}.`);
}
if (!Array.isArray(facts.cleanupErrors) || facts.cleanupErrors.length > 0) {
fail(`${lane} lane cleanup was incomplete.`);
}
if (!isRecord(facts.observation) || facts.observation.truncated === true) {
fail(`${lane} event recording is missing or truncated.`);
}
if (!new Set(["blocked", "complete"]).has(facts.status)) {
fail(`${lane} lane ended with ${facts.status ?? "no status"}: ${facts.error ?? "no detail"}`);
}
const laneDir = path.join(outputDir, lane);
fs.mkdirSync(laneDir, { recursive: true });
const artifacts = [];
const copied = new Set();
const records = isRecord(facts.artifacts) ? facts.artifacts : {};
if (facts.status === "complete") {
for (const name of Object.keys(MEDIA)) {
artifactRecord(records[name], lane, name, publishedRoot);
}
}
for (const [name, media] of Object.entries(MEDIA)) {
const record = records[name];
if (!record) {
continue;
}
const source = artifactRecord(record, lane, name, publishedRoot);
const filename = `${lane}-${name}.${media.extension}`;
copy(source, path.join(laneDir, filename));
copied.add(record.file);
artifacts.push({
alt: `${lane} ${name}`,
inline: name === "previewGifCropped",
kind: media.kind,
label: lane === "baseline" ? "Before — current main" : "After — this PR",
lane,
path: `${lane}/${filename}`,
required: facts.status === "complete",
targetPath: `${lane}/${filename}`,
});
}
const trustedLaneDir = path.join(publishedRoot, lane);
for (const entry of fs.readdirSync(trustedLaneDir, { withFileTypes: true })) {
if (!entry.isFile() || copied.has(entry.name) || entry.name === "mantis-lane-facts.json") {
continue;
}
copy(path.join(trustedLaneDir, entry.name), path.join(laneDir, entry.name));
artifacts.push({
kind: "attachment",
label: `${lane} ${entry.name}`,
lane,
path: `${lane}/${entry.name}`,
required: false,
targetPath: `${lane}/${entry.name}`,
});
}
copy(factsFile, path.join(laneDir, "mantis-lane-facts.json"));
artifacts.push({
kind: "attachment",
label: `${lane} complete recorded facts`,
lane,
path: `${lane}/mantis-lane-facts.json`,
required: true,
targetPath: `${lane}/mantis-lane-facts.json`,
});
return { artifacts, facts, status: facts.status === "complete" ? "pass" : "blocked" };
}
function collectProof(options) {
const judgment = validateAgentJudgment(options.agentManifest);
if (fs.existsSync(options.outputDir)) {
fail(`Trusted output already exists: ${options.outputDir}`);
}
const baseline = loadLane({
factsFile: options.baselineFacts,
expectedSha: options.baselineSha,
lane: "baseline",
outputDir: options.outputDir,
publishedRoot: options.publishedRoot,
});
const candidate = loadLane({
factsFile: options.candidateFacts,
expectedSha: options.candidateSha,
lane: "candidate",
outputDir: options.outputDir,
publishedRoot: options.publishedRoot,
});
let outcome = judgment.comparison.outcome;
if (outcome === "pass" && (baseline.status !== "pass" || candidate.status !== "pass")) {
outcome = "fail";
}
copy(options.agentManifest, path.join(options.outputDir, "agent-judgment.json"));
const artifacts = [
...baseline.artifacts,
...candidate.artifacts,
{
kind: "attachment",
label: "Agent judgment",
lane: "run",
path: "agent-judgment.json",
required: true,
targetPath: "agent-judgment.json",
},
];
const laneComparison = (lane, loaded, ref, sha) => ({
detail: lane.detail,
expectationMet: lane.expectationMet && loaded.status === "pass",
expected: lane.expected,
ref,
sha,
status: loaded.status,
});
const runtimeSeconds = Math.round(
(Number(baseline.facts.observation.uptimeMs ?? 0) +
Number(candidate.facts.observation.uptimeMs ?? 0)) /
1_000,
);
const manifest = {
artifacts,
comparison: {
baseline: laneComparison(judgment.comparison.baseline, baseline, "main", options.baselineSha),
candidate: laneComparison(
judgment.comparison.candidate,
candidate,
options.candidateSha,
options.candidateSha,
),
differential: judgment.comparison.differential,
outcome,
pass: outcome === "pass",
},
id: "telegram-visible-proof",
runtimeSeconds,
scenario: judgment.scenario,
schemaVersion: 2,
summary: judgment.summary,
title: judgment.title,
};
writeJson(path.join(options.outputDir, "mantis-evidence.json"), manifest);
return manifest;
}
function main(argv = process.argv.slice(2)) {
const [command, ...rest] = argv;
if (command !== "collect") {
fail("Usage: telegram-visible-proof.mjs collect [arguments]");
}
const args = parseArgs(rest);
const manifest = collectProof({
agentManifest: requiredArg(args, "agent_manifest"),
baselineFacts: requiredArg(args, "baseline_facts"),
baselineSha: requiredArg(args, "baseline_sha"),
candidateFacts: requiredArg(args, "candidate_facts"),
candidateSha: requiredArg(args, "candidate_sha"),
outputDir: requiredArg(args, "output_dir"),
publishedRoot: requiredArg(args, "published_root"),
});
console.log(JSON.stringify({ outcome: manifest.comparison.outcome }));
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
}