Files
pikasTech-unidesk/scripts/host-codex-commander-no-daemon-smoke-contract-test.ts
T
2026-05-21 12:51:53 +00:00

171 lines
7.7 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createCommanderRequestHandler, type RuntimeConfig } from "../src/components/microservices/host-codex-commander/src/index";
import { commanderHealth, summarizeCommanderTrace } from "../src/components/microservices/host-codex-commander/src/state";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown, label: string): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
return value as JsonRecord;
}
function asRecordArray(value: unknown, label: string): JsonRecord[] {
assertCondition(Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null && !Array.isArray(item)), `${label} must be object array`, value);
return value as JsonRecord[];
}
function asStringArray(value: unknown, label: string): string[] {
assertCondition(Array.isArray(value) && value.every((item) => typeof item === "string"), `${label} must be string array`, value);
return value as string[];
}
function runCli(args: string[], expectStatus: number): JsonRecord {
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
cwd: process.cwd(),
encoding: "utf8",
maxBuffer: 4 * 1024 * 1024,
});
assertCondition(result.status === expectStatus, `status mismatch for ${args.join(" ")}`, {
status: result.status,
stdout: result.stdout.slice(-2000),
stderr: result.stderr.slice(-2000),
});
assertCondition(result.stdout.trim().length > 0, `command produced no stdout: ${args.join(" ")}`);
return asRecord(JSON.parse(result.stdout) as unknown, "cli envelope");
}
function dataOf(envelope: JsonRecord): JsonRecord {
return asRecord(envelope.data, "data");
}
async function readJson(response: Response): Promise<JsonRecord> {
return asRecord(await response.json() as unknown, "response body");
}
const sessionId = `no-daemon-smoke-contract-${process.pid}`;
const liveSessionPath = join(process.cwd(), ".state", "commander", "sessions", `${sessionId}.json`);
assertCondition(!existsSync(liveSessionPath), "precondition failed: smoke session path should not already exist", liveSessionPath);
const smoke = dataOf(runCli(["commander", "smoke", "--dry-run", "--session-id", sessionId], 0));
assertCondition(smoke.ok === true, "smoke must succeed", smoke);
assertCondition(smoke.phase === "source-contract", "smoke must remain source-contract phase", smoke);
assertCondition(smoke.mode === "dry-run", "smoke must report dry-run mode", smoke);
assertCondition(smoke.mutation === false, "smoke must be non-mutating", smoke);
const noDaemon = asRecord(smoke.noDaemonSmokeContract, "noDaemonSmokeContract");
for (const flag of [
"startsDaemon",
"startsPtyBridge",
"startsStdioBridge",
"opensSshBridge",
"sendsClaudeqq",
"restartsServices",
"interruptsTasks",
"cancelsTasks",
"deploys",
"runsFullCheckOrE2e",
]) {
assertCondition(noDaemon[flag] === false, `${flag} must be false`, noDaemon);
}
assertCondition(asStringArray(noDaemon.allowedCommands, "allowedCommands").includes("bun scripts/host-codex-commander-no-daemon-smoke-contract-test.ts"), "smoke should name this lightweight contract", noDaemon);
const validationPlan = asRecordArray(smoke.validationPlan, "validationPlan");
const surfaces = validationPlan.map((item) => item.surface);
for (const expected of [
"health endpoint",
"state file",
"trace summary dry-run",
"approval draft preview",
"SSH bridge boundary",
]) {
assertCondition(surfaces.includes(expected), `missing validation surface ${expected}`, surfaces);
}
for (const item of validationPlan) {
assertCondition(asStringArray(item.expectedEvidence, "expectedEvidence").length > 0, "each validation item must define evidence", item);
assertCondition(asStringArray(item.noRuntimeSideEffects, "noRuntimeSideEffects").length > 0, "each validation item must define no-side-effect boundary", item);
}
const smokeWithoutDryRun = dataOf(runCli(["commander", "smoke", "--session-id", sessionId], 1));
assertCondition(smokeWithoutDryRun.error === "dry-run-required", "smoke must require --dry-run", smokeWithoutDryRun);
assertCondition(!existsSync(liveSessionPath), "smoke CLI must not write live commander state", liveSessionPath);
const tmp = mkdtempSync(join(tmpdir(), "host-codex-commander-smoke-"));
try {
const runtime: RuntimeConfig = {
rootDir: tmp,
host: "127.0.0.1",
port: 4261,
logFile: join(tmp, "logs", "commander.jsonl"),
serviceId: "host-codex-commander",
stateRoot: tmp,
sessionId,
};
const health = commanderHealth(runtime, "2026-05-21T00:00:00.000Z");
assertCondition(health.ok === true && health.service === "host-codex-commander", "health helper must expose service metadata", health);
assertCondition(health.stateRoot === tmp, "health helper must use temp state root", health);
const handler = createCommanderRequestHandler(runtime);
const healthBody = await readJson(await handler(new Request("http://localhost/health")));
assertCondition(healthBody.ok === true, "short-lived handler health route must succeed without Bun.serve", healthBody);
const trace = summarizeCommanderTrace({
taskId: "task-smoke",
sessionId,
traceJsonl: [
JSON.stringify({ seq: 1, kind: "message", status: "running", summary: "checking token=ghp_1234567890abcdef" }),
JSON.stringify({ seq: 2, kind: "event", status: "attention_required", text: "needs approval" }),
].join("\n"),
taskSummary: "summary password=secret",
});
assertCondition(trace.taskId === "task-smoke", "trace summary must preserve task id", trace);
assertCondition(trace.sessionId === sessionId, "trace summary must preserve session id", trace);
assertCondition(trace.lastSeq === 2, "trace summary must compute last seq", trace);
assertCondition(trace.status === "attention_required", "trace summary must derive attention_required status", trace);
assertCondition(trace.redactionsApplied >= 2, "trace summary must redact mock secrets", trace);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
const approval = dataOf(runCli([
"commander",
"approval",
"request",
"--action",
"code-queue-task-cancel",
"--reason",
"token=ghp_1234567890abcdef",
"--dry-run",
], 0));
const claudeqq = asRecord(approval.claudeqq, "claudeqq");
assertCondition(claudeqq.mutation === false, "approval preview must not mutate ClaudeQQ", claudeqq);
assertCondition(claudeqq.sendImplemented === false, "approval preview must not implement sending", claudeqq);
assertCondition(!JSON.stringify(approval).includes("ghp_1234567890abcdef"), "approval preview must redact secret-like reason", approval);
const doc = readFileSync("docs/reference/host-codex-commander.md", "utf8");
for (const snippet of [
"commander smoke --dry-run",
"无 daemon smoke contract",
"health endpoint",
"SSH bridge boundary",
]) {
assertCondition(doc.includes(snippet), `reference doc missing snippet: ${snippet}`);
}
process.stdout.write(`${JSON.stringify({
ok: true,
checks: [
"commander smoke --dry-run is non-mutating and dry-run required",
"no-daemon smoke contract forbids daemon, SSH/PTY/stdio bridge, ClaudeQQ send, restart, interrupt, cancel, deploy, and full e2e",
"health endpoint and trace summary are validated through short-lived source-level helpers",
"approval draft preview remains sendImplemented=false and redacted",
"reference doc describes the dev validation surfaces and no-daemon boundary",
],
}, null, 2)}\n`);