mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
0b4fd11c8c
Complete the isolated published installation before starting the protocol deadline, disable dependency lifecycle scripts consistently with package verification, and reuse that installation and cache for update validation.
271 lines
8.7 KiB
JavaScript
271 lines
8.7 KiB
JavaScript
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { delimiter, join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
import { lt, rcompare } from "semver";
|
|
|
|
import { CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
|
|
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
|
import {
|
|
requireMcpLifecycleResult,
|
|
sortVerifierNames,
|
|
} from "./lib/mcp-verifier-results.mjs";
|
|
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
|
|
|
const verifierRun = createVerifierRun();
|
|
const execFileAsync = promisify(execFile);
|
|
const version = process.argv[2];
|
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(version ?? ""))
|
|
throw new Error("Usage: node scripts/verify-published-package.mjs <version>");
|
|
|
|
const canaryRoot = await mkdtemp(join(tmpdir(), "rea-published-canary-"));
|
|
const serverEnvironment = {
|
|
...process.env,
|
|
npm_config_cache: join(canaryRoot, "npm-cache"),
|
|
};
|
|
delete serverEnvironment.HOPPER_TARGET_PATH;
|
|
const client = new Client({
|
|
name: "published-package-canary",
|
|
version: "1",
|
|
});
|
|
let stderr = "";
|
|
let transport;
|
|
|
|
let publishedToolCount = 0;
|
|
let update;
|
|
|
|
try {
|
|
// Registry installation has its own lifetime; it must finish before the
|
|
// MCP handshake deadline starts. Reuse this exact installation for update.
|
|
const publishedPrefix = join(canaryRoot, "published");
|
|
await execFileAsync(
|
|
"npm",
|
|
[
|
|
"install",
|
|
"--ignore-scripts",
|
|
"--no-save",
|
|
"--no-package-lock",
|
|
"--prefix",
|
|
publishedPrefix,
|
|
`rea-agents@${version}`,
|
|
],
|
|
{ cwd: canaryRoot, env: serverEnvironment, maxBuffer: 16 * 1024 * 1024 },
|
|
);
|
|
const publishedRoot = join(publishedPrefix, "node_modules", "rea-agents");
|
|
const installed = JSON.parse(
|
|
await readFile(join(publishedRoot, "package.json"), "utf8"),
|
|
);
|
|
if (installed.version !== version)
|
|
throw new Error(
|
|
`Published installation is ${installed.version}, expected ${version}`,
|
|
);
|
|
const launcher = join(publishedRoot, "scripts", "rea.mjs");
|
|
for (const option of ["--help", "--llms"])
|
|
await execFileAsync(process.execPath, [launcher, option], {
|
|
cwd: canaryRoot,
|
|
env: serverEnvironment,
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
});
|
|
transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [launcher, "mcp"],
|
|
cwd: canaryRoot,
|
|
env: serverEnvironment,
|
|
stderr: "pipe",
|
|
});
|
|
transport.stderr?.on("data", (chunk) => {
|
|
if (stderr.length < 16_384) stderr += chunk.toString("utf8");
|
|
});
|
|
await client.connect(transport);
|
|
const tools = await client.listTools();
|
|
const toolNames = tools.tools.map(({ name }) => name);
|
|
const canonicalToolNames = new Set(TOOL_CONTRACTS.map(({ name }) => name));
|
|
const unknownToolNames = toolNames.filter(
|
|
(name) => !canonicalToolNames.has(name),
|
|
);
|
|
const publishedToolNames = new Set(toolNames);
|
|
const missingToolNames = [...canonicalToolNames].filter(
|
|
(name) => !publishedToolNames.has(name),
|
|
);
|
|
publishedToolCount = toolNames.length;
|
|
if (
|
|
publishedToolCount === 0 ||
|
|
publishedToolNames.size !== publishedToolCount ||
|
|
unknownToolNames.length > 0 ||
|
|
missingToolNames.length > 0
|
|
)
|
|
throw new Error(
|
|
`Published MCP exposed an invalid canonical tool catalog (${String(publishedToolCount)} tools, unknown: ${unknownToolNames.join(", ") || "none"}, missing: ${missingToolNames.join(", ") || "none"})`,
|
|
);
|
|
const statusReply = await client.callTool({
|
|
name: "binary_session",
|
|
arguments: {},
|
|
});
|
|
const status = requireMcpLifecycleResult(statusReply, "binary_session");
|
|
const availabilityNames = sortVerifierNames(
|
|
status.tool_availability.map(({ name }) => name),
|
|
);
|
|
if (
|
|
JSON.stringify(availabilityNames) !==
|
|
JSON.stringify(sortVerifierNames(canonicalToolNames))
|
|
)
|
|
throw new Error(
|
|
"Published binary_session omitted canonical tool availability",
|
|
);
|
|
update = await verifyPublishedUpdate({
|
|
version,
|
|
canaryRoot,
|
|
updaterRoot: publishedRoot,
|
|
});
|
|
} catch (cause) {
|
|
throw new Error(`Published package verification failed: ${stderr}`, {
|
|
cause,
|
|
});
|
|
} finally {
|
|
try {
|
|
await client.close();
|
|
await transport?.close();
|
|
} finally {
|
|
await rm(canaryRoot, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
process.stdout.write(
|
|
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), package: "rea-agents", version, mcpTools: publishedToolCount, canonicalMcpTools: CATALOG_IDENTITY.counts.mcp_tools, update })}\n`,
|
|
);
|
|
|
|
async function verifyPublishedUpdate({
|
|
version: targetVersion,
|
|
canaryRoot,
|
|
updaterRoot,
|
|
}) {
|
|
const prefix = join(canaryRoot, "update-prefix");
|
|
const home = join(canaryRoot, "update-home");
|
|
const environment = {
|
|
...process.env,
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
APPDATA: join(home, "AppData", "Roaming"),
|
|
LOCALAPPDATA: join(home, "AppData", "Local"),
|
|
CLAUDE_CONFIG_DIR: home,
|
|
CODEX_HOME: join(home, ".codex"),
|
|
COPILOT_HOME: join(home, ".copilot"),
|
|
GROK_HOME: join(home, ".grok"),
|
|
SAND_DATA_ROOT: join(home, ".grokbot"),
|
|
OMP_PROFILE: undefined,
|
|
PI_CODING_AGENT_DIR: undefined,
|
|
PI_CONFIG_DIR: undefined,
|
|
PI_PROFILE: undefined,
|
|
XDG_CONFIG_HOME: join(home, ".config"),
|
|
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.json"),
|
|
npm_config_cache: join(canaryRoot, "npm-cache"),
|
|
npm_config_prefix: prefix,
|
|
PATH: `${join(prefix, "bin")}${delimiter}${process.env.PATH ?? ""}`,
|
|
};
|
|
const previousVersion = await findPreviousPublishedVersion(
|
|
targetVersion,
|
|
canaryRoot,
|
|
environment,
|
|
);
|
|
|
|
await execFileAsync(
|
|
"npm",
|
|
[
|
|
"install",
|
|
"--global",
|
|
"--ignore-scripts",
|
|
"--prefix",
|
|
prefix,
|
|
`rea-agents@${previousVersion}`,
|
|
],
|
|
{ cwd: canaryRoot, env: environment, maxBuffer: 16 * 1024 * 1024 },
|
|
);
|
|
|
|
const npmRoot = (
|
|
await execFileAsync("npm", ["root", "--global"], {
|
|
cwd: canaryRoot,
|
|
env: environment,
|
|
})
|
|
).stdout.trim();
|
|
// Older releases may predate the update command. Exercise the new published
|
|
// updater's production workflow against that real previous installation.
|
|
const harness = join(canaryRoot, "update.mjs");
|
|
await writeFile(
|
|
harness,
|
|
`
|
|
import { runUpdate } from ${JSON.stringify(pathToFileURL(join(updaterRoot, "dist/application/Update.js")).href)};
|
|
import { systemUpdateHost } from ${JSON.stringify(pathToFileURL(join(updaterRoot, "dist/application/UpdateRuntime.js")).href)};
|
|
const result = await runUpdate(${JSON.stringify(previousVersion)}, systemUpdateHost(${JSON.stringify(join(npmRoot, "rea-agents"))}, ${JSON.stringify(home)}), "structured");
|
|
process.stdout.write(JSON.stringify(result));
|
|
if (result.status === "failed") process.exitCode = 1;
|
|
`,
|
|
);
|
|
const { stdout } = await execFileAsync(process.execPath, [harness], {
|
|
cwd: canaryRoot,
|
|
env: environment,
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
});
|
|
const result = JSON.parse(stdout);
|
|
if (
|
|
result.status !== "updated" ||
|
|
result.previousVersion !== previousVersion ||
|
|
result.latestVersion !== targetVersion ||
|
|
result.installedVersion !== targetVersion ||
|
|
result.command.at(-1) !== `rea-agents@${targetVersion}`
|
|
)
|
|
throw new Error(
|
|
`Published REA updater did not update ${previousVersion} to ${targetVersion}: ${stdout}`,
|
|
);
|
|
|
|
const installed = JSON.parse(
|
|
await readFile(join(npmRoot, "rea-agents", "package.json"), "utf8"),
|
|
);
|
|
if (installed.version !== targetVersion)
|
|
throw new Error(
|
|
`REA update installed ${installed.version}, expected ${targetVersion}`,
|
|
);
|
|
|
|
const help = (
|
|
await execFileAsync(join(prefix, "bin", "rea-agents"), ["--help"], {
|
|
cwd: canaryRoot,
|
|
env: environment,
|
|
})
|
|
).stdout;
|
|
if (!/^\s{2}update\s/mu.test(help) || /^\s{2}upgrade\s/mu.test(help))
|
|
throw new Error(
|
|
"the updated executable did not expose only the update command",
|
|
);
|
|
|
|
return {
|
|
status: result.status,
|
|
previousVersion,
|
|
latestVersion: result.latestVersion,
|
|
installedVersion: installed.version,
|
|
};
|
|
}
|
|
|
|
async function findPreviousPublishedVersion(targetVersion, cwd, environment) {
|
|
const { stdout } = await execFileAsync(
|
|
"npm",
|
|
["view", "rea-agents", "versions", "--json"],
|
|
{ cwd, env: environment, maxBuffer: 16 * 1024 * 1024 },
|
|
);
|
|
const versions = JSON.parse(stdout);
|
|
const previous = versions
|
|
.filter(
|
|
(candidate) =>
|
|
typeof candidate === "string" && lt(candidate, targetVersion),
|
|
)
|
|
.sort(rcompare)[0];
|
|
if (previous === undefined)
|
|
throw new Error(
|
|
`No published rea-agents version precedes ${targetVersion}`,
|
|
);
|
|
return previous;
|
|
}
|