Files
openclaw/extensions/codex/src/app-server/runtime-artifact.test.ts
Peter Steinberger 96f0983a85 fix(onboarding): skip setup for configured gateways and require inference first (#102883)
* fix(crestodian): keep onboarding RPCs restart-safe

* fix(profiles): isolate approval state migrations

* fix(crestodian): bypass configured gateway setup

* test(crestodian): type onboarding mocks

* fix(onboarding): require inference before Crestodian

* fix(onboarding): enforce verified inference handoff

* fix(macos): reset setup on gateway endpoint edits

* chore(i18n): refresh native source inventory

* fix(gateway): keep socket on request cancellation

* test(packaging): require workspace templates

* fix(onboarding): bind setup to verified inference

* fix(onboarding): align inference gate contracts

* fix(crestodian): classify concurrent policy rejection

* test(crestodian): expect registry restoration

* fix(onboarding): bind setup to configured gateways

* fix(codex): preserve startup phase deadlines

* test(crestodian): match fail-closed policy ordering

* test(onboarding): assert bound gateway handoff

* fix(codex): bind runtime resolution to spawn cwd

* test(crestodian): assert policy rejection order

* fix(cli): preserve gateway routing across restarts

* fix(macos): fail closed during gateway edits

* test(macos): cover gateway route generation races

* chore: keep release notes out of onboarding PR

* fix(ci): refresh onboarding generated checks

* style(swift): align gateway channel formatting

* fix(ci): refresh plugin SDK surface budgets

* fix(ci): resync native string inventory

* refactor(swift): split gateway channel support

* test(doctor): isolate plugin compatibility registry

* test(macos): isolate gateway onboarding fixtures

* test(macos): assert gateway lease health ordering

* fix(codex): reconcile computer-use startup changes
2026-07-11 10:25:14 -07:00

398 lines
16 KiB
TypeScript

import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { withTempDir } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import type { CodexAppServerClient } from "./client.js";
import type { CodexAppServerStartOptions } from "./config.js";
import {
bindCodexAppServerRuntimeArtifact,
captureCodexAppServerRuntimeArtifactBeforeStart,
finalizeCodexAppServerRuntimeArtifact,
readCodexAppServerClientRuntimeArtifact,
validateCodexAppServerRuntimeArtifact,
} from "./runtime-artifact.js";
function startOptions(
command: string,
overrides: Partial<CodexAppServerStartOptions> = {},
): CodexAppServerStartOptions {
return {
transport: "stdio",
command,
commandSource: "config",
args: ["app-server"],
headers: {},
...overrides,
};
}
function spawnIdentity(options: CodexAppServerStartOptions, nativeCommand?: string) {
return {
command: options.command,
argsFingerprint: createHash("sha256").update(JSON.stringify(options.args)).digest("hex"),
...(options.commandSource ? { commandSource: options.commandSource } : {}),
...(options.managedCommandOrder ? { managedCommandOrder: options.managedCommandOrder } : {}),
...(nativeCommand ? { nativeCommand } : {}),
};
}
async function captureBinding(params: {
options: CodexAppServerStartOptions;
nativeCommand?: string;
}) {
const client = {} as CodexAppServerClient;
const identity = spawnIdentity(params.options, params.nativeCommand);
const before = await captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: params.options,
spawnIdentity: identity,
});
const binding = await finalizeCodexAppServerRuntimeArtifact({
before,
startOptions: params.options,
spawnIdentity: identity,
runtimeIdentity: { serverVersion: "0.144.1", userAgent: "codex-test" },
});
bindCodexAppServerRuntimeArtifact(client, binding);
return { binding, client };
}
describe("Codex app-server runtime artifact", () => {
it("binds a native executable and its adjacent code-mode host", async () => {
await withTempDir("openclaw-codex-runtime-artifact-", async (root) => {
const command = path.join(root, "codex");
const codeModeHost = path.join(root, "codex-code-mode-host");
await fs.writeFile(command, "native-v1");
await fs.writeFile(codeModeHost, "host-v1");
const { binding, client } = await captureBinding({ options: startOptions(command) });
expect(readCodexAppServerClientRuntimeArtifact(client)).toEqual(binding);
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(true);
await fs.writeFile(codeModeHost, "host-v2");
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
});
});
it.runIf(process.platform !== "win32")(
"resolves relative launch paths and shebang targets from the spawn cwd",
async () => {
await withTempDir("openclaw-codex-runtime-cwd-", async (root) => {
const spawnCwd = path.join(root, "workspace");
const binDir = path.join(spawnCwd, "bin");
const interpreterDir = path.join(spawnCwd, "interpreters");
const nativeDir = path.join(spawnCwd, "native");
await Promise.all([
fs.mkdir(binDir, { recursive: true }),
fs.mkdir(interpreterDir, { recursive: true }),
fs.mkdir(nativeDir, { recursive: true }),
]);
const command = path.join(binDir, "codex");
const interpreter = path.join(interpreterDir, "fixture-node");
const nativeCommand = path.join(nativeDir, "codex-native");
await Promise.all([
fs.writeFile(command, "#!/usr/bin/env fixture-node\n"),
fs.writeFile(interpreter, "interpreter-v1"),
fs.writeFile(nativeCommand, "native-v1"),
]);
await Promise.all([
fs.chmod(command, 0o755),
fs.chmod(interpreter, 0o755),
fs.chmod(nativeCommand, 0o755),
]);
const options = startOptions("codex", {
cwd: spawnCwd,
env: { PATH: ["bin", "interpreters"].join(path.delimiter) },
});
const { binding } = await captureBinding({
options,
nativeCommand: path.join("native", "codex-native"),
});
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(true);
await fs.writeFile(interpreter, "interpreter-v2");
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
});
},
);
it("attests that an adjacent code-mode host is absent", async () => {
await withTempDir("openclaw-codex-runtime-no-host-", async (root) => {
const command = path.join(root, "codex");
const codeModeHost = path.join(root, "codex-code-mode-host");
await fs.writeFile(command, "native-v1");
const { binding } = await captureBinding({ options: startOptions(command) });
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(true);
await fs.writeFile(codeModeHost, "host-v1");
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
});
});
it("binds the complete canonical package tree", async () => {
await withTempDir("openclaw-codex-package-artifact-", async (root) => {
const binDir = path.join(root, "bin");
const resourcesDir = path.join(root, "codex-resources");
const pathDir = path.join(root, "codex-path");
await Promise.all([
fs.mkdir(binDir, { recursive: true }),
fs.mkdir(resourcesDir, { recursive: true }),
fs.mkdir(pathDir, { recursive: true }),
]);
const command = path.join(binDir, "codex");
await fs.writeFile(command, "native-v1");
await fs.writeFile(path.join(binDir, "codex-code-mode-host"), "host-v1");
await fs.writeFile(path.join(resourcesDir, "bwrap"), "resource-v1");
await fs.writeFile(path.join(pathDir, "rg"), "rg-v1");
await fs.writeFile(path.join(root, "codex-package.json"), '{"layoutVersion":1}\n');
const { binding } = await captureBinding({ options: startOptions(command) });
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(true);
await fs.writeFile(path.join(resourcesDir, "bwrap"), "resource-v2");
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
});
});
it("produces the same package binding regardless of directory enumeration order", async () => {
await withTempDir("openclaw-codex-package-order-", async (root) => {
const binDir = path.join(root, "bin");
await fs.mkdir(binDir, { recursive: true });
const command = path.join(binDir, "codex");
await fs.writeFile(command, "native-v1");
await fs.writeFile(path.join(root, "codex-package.json"), "{}\n");
await fs.writeFile(path.join(root, "z-resource"), "z");
await fs.writeFile(path.join(root, "a-resource"), "a");
const options = startOptions(command);
const first = await captureBinding({ options });
await fs.rm(path.join(root, "z-resource"));
await fs.rm(path.join(root, "a-resource"));
await fs.writeFile(path.join(root, "a-resource"), "a");
await fs.writeFile(path.join(root, "z-resource"), "z");
const second = await captureBinding({ options });
expect(second.binding).toEqual(first.binding);
});
});
it("binds an explicit code-mode host override", async () => {
await withTempDir("openclaw-codex-host-override-", async (root) => {
const command = path.join(root, "codex");
const codeModeHost = path.join(root, "custom-code-mode-host");
await fs.writeFile(command, "native-v1");
await fs.writeFile(codeModeHost, "host-v1");
const options = startOptions(command, {
env: { CODEX_CODE_MODE_HOST_PATH: codeModeHost },
});
const { binding } = await captureBinding({ options });
await fs.writeFile(codeModeHost, "host-v2");
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
});
});
it("detects candidate bytes changing between spawn snapshots", async () => {
await withTempDir("openclaw-codex-runtime-race-", async (root) => {
const command = path.join(root, "codex");
await fs.writeFile(command, "native-v1");
const options = startOptions(command);
const identity = spawnIdentity(options);
const before = await captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: identity,
});
await fs.writeFile(command, "native-v2");
await expect(
finalizeCodexAppServerRuntimeArtifact({
before,
startOptions: options,
spawnIdentity: identity,
runtimeIdentity: { serverVersion: "0.144.1" },
}),
).rejects.toThrow("changed during startup");
});
});
it("keeps raw argv out of the server-minted artifact id", async () => {
await withTempDir("openclaw-codex-runtime-secret-", async (root) => {
const command = path.join(root, "codex");
await fs.writeFile(command, "native-v1");
const secret = "provider.api_key=super-secret-value";
const options = startOptions(command, { args: ["-c", secret, "app-server"] });
const { binding } = await captureBinding({ options });
expect(binding.id).not.toContain(secret);
expect(JSON.stringify(binding)).not.toContain("super-secret-value");
});
});
it("fails closed for remote WebSocket runtimes", async () => {
const options = startOptions("codex", {
transport: "websocket",
url: "ws://127.0.0.1:1234",
});
await expect(
captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: spawnIdentity(options),
}),
).rejects.toThrow("WebSocket attestation is unsupported");
});
it("fails closed when spawn environment can inject runtime code", async () => {
const options = startOptions("codex", { env: { NODE_OPTIONS: "--require=/tmp/inject.js" } });
await expect(
captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: spawnIdentity(options),
}),
).rejects.toThrow("injected runtime environment: NODE_OPTIONS");
});
it("allows bounded Node resource and warning options", async () => {
await withTempDir("openclaw-codex-runtime-node-options-", async (root) => {
const command = path.join(root, "codex");
await fs.writeFile(command, "native-v1");
const options = startOptions(command, {
env: {
NODE_OPTIONS:
"--max-old-space-size=4096 --no-warnings --disable-warning=ExperimentalWarning",
},
});
await expect(captureBinding({ options })).resolves.toMatchObject({
binding: { id: expect.stringMatching(/^codex-app-server:v1:/u) },
});
});
});
it("binds the Windows npm shim, Node entrypoint, native binary, and mixed-case host override", async () => {
await withTempDir("openclaw-codex-runtime-windows-", async (root) => {
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
const originalExecPath = Object.getOwnPropertyDescriptor(process, "execPath");
if (!originalPlatform || !originalExecPath) {
throw new Error("expected configurable process runtime descriptors");
}
const nodePath = path.join(root, "node.exe");
const shimPath = path.join(root, "codex.cmd");
const entryPath = path.join(root, "node_modules", "@openai", "codex", "bin", "codex.js");
const packageRoot = path.join(root, "vendor", "x86_64-pc-windows-msvc");
const binDir = path.join(packageRoot, "bin");
const nativePath = path.join(binDir, "codex.exe");
const hostPath = path.join(root, "custom-host.exe");
await Promise.all([
fs.mkdir(path.dirname(entryPath), { recursive: true }),
fs.mkdir(binDir, { recursive: true }),
]);
await Promise.all([
fs.writeFile(nodePath, "node-v1"),
fs.writeFile(entryPath, "entry-v1"),
fs.writeFile(nativePath, "native-v1"),
fs.writeFile(hostPath, "host-v1"),
fs.writeFile(path.join(packageRoot, "codex-package.json"), "{}\n"),
fs.writeFile(
shimPath,
'@ECHO off\r\n"%~dp0\\node_modules\\@openai\\codex\\bin\\codex.js" %*\r\n',
),
]);
try {
Object.defineProperty(process, "platform", { ...originalPlatform, value: "win32" });
Object.defineProperty(process, "execPath", { ...originalExecPath, value: nodePath });
const options = startOptions("codex", {
env: {
PATH: root,
PATHEXT: ".CMD;.EXE;.BAT",
Codex_Code_Mode_Host_Path: hostPath,
},
});
const { binding } = await captureBinding({ options, nativeCommand: nativePath });
for (const [filePath, replacement] of [
[nodePath, "node-v2"],
[entryPath, "entry-v2"],
[nativePath, "native-v2"],
[hostPath, "host-v2"],
] as const) {
const original = await fs.readFile(filePath);
await fs.writeFile(filePath, replacement);
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(false);
await fs.writeFile(filePath, original);
await expect(validateCodexAppServerRuntimeArtifact(binding)).resolves.toBe(true);
}
} finally {
Object.defineProperty(process, "platform", originalPlatform);
Object.defineProperty(process, "execPath", originalExecPath);
}
});
});
it("rejects malformed and oversized server-minted ids without filesystem access", async () => {
const fingerprint = "0".repeat(64);
await expect(
validateCodexAppServerRuntimeArtifact({ id: "codex-app-server:v1:wrong", fingerprint }),
).resolves.toBe(false);
await expect(
validateCodexAppServerRuntimeArtifact({
id: `codex-app-server:v1:${"a".repeat(32 * 1024)}`,
fingerprint,
}),
).resolves.toBe(false);
});
it("rejects packages beyond the bounded directory depth", async () => {
await withTempDir("openclaw-codex-package-depth-", async (root) => {
const binDir = path.join(root, "bin");
await fs.mkdir(binDir, { recursive: true });
const command = path.join(binDir, "codex");
await fs.writeFile(command, "native-v1");
await fs.writeFile(path.join(root, "codex-package.json"), "{}\n");
const deep = path.join(root, ...Array.from({ length: 66 }, (_, index) => `d${index}`));
await fs.mkdir(deep, { recursive: true });
await fs.writeFile(path.join(deep, "resource"), "x");
const options = startOptions(command);
await expect(
captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: spawnIdentity(options),
}),
).rejects.toThrow("bounded directory depth");
});
});
it("honors an already-aborted bounded capture", async () => {
const controller = new AbortController();
controller.abort(new Error("stop hashing"));
const options = startOptions("codex");
await expect(
captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: spawnIdentity(options),
signal: controller.signal,
}),
).rejects.toThrow("stop hashing");
});
it.runIf(process.platform !== "win32")("rejects symlinks inside a package artifact", async () => {
await withTempDir("openclaw-codex-package-link-", async (root) => {
const binDir = path.join(root, "bin");
await fs.mkdir(binDir, { recursive: true });
const command = path.join(binDir, "codex");
await fs.writeFile(command, "native-v1");
await fs.writeFile(path.join(root, "codex-package.json"), "{}\n");
await fs.symlink(command, path.join(root, "linked-runtime"));
const options = startOptions(command);
await expect(
captureCodexAppServerRuntimeArtifactBeforeStart({
startOptions: options,
spawnIdentity: spawnIdentity(options),
}),
).rejects.toThrow("unsupported entry");
});
});
});