import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; type JsonRecord = Record; 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 asStringArray(value: unknown, label: string): string[] { assertCondition(Array.isArray(value) && value.every((item) => typeof item === "string"), `${label} must be a 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), }); return asRecord(JSON.parse(result.stdout) as unknown, "cli envelope"); } function dataOf(envelope: JsonRecord): JsonRecord { return asRecord(envelope.data, "data"); } const contract = dataOf(runCli(["commander", "contract"], 0)); assertCondition(contract.phase === "source-contract", "contract must identify source-contract phase", contract); assertCondition(contract.serviceId === "host-codex-commander", "contract must expose service id", contract); assertCondition(contract.daemonImplemented === false, "daemon must not be implemented in phase one", contract); assertCondition(contract.liveOperationsImplemented === false, "live operations must not be implemented in phase one", contract); const capabilities = asStringArray(contract.requiredCapabilities, "requiredCapabilities"); for (const expected of [ "host-codex-process-discovery", "ssh-bridge-contract", "pty-bridge-contract", "stdio-bridge-contract", "prompt-guidance-plan", "trace-summary-plan", "issue-20-board-read-write-entry", "issue-46-brief-read-write-entry", "pr-closeout-boundary-plan", "claudeqq-high-risk-approval-entry", ]) { assertCondition(capabilities.includes(expected), `missing required capability ${expected}`, capabilities); } const safety = asRecord(contract.safetyBoundary, "safetyBoundary"); assertCondition(safety.phaseOneMutationAllowed === false, "phase one must forbid mutation", safety); const forbidden = asStringArray(safety.forbiddenWithoutExplicitUserApproval, "forbiddenWithoutExplicitUserApproval"); assertCondition(forbidden.includes("code-queue-backend-restart"), "backend restart must require approval", forbidden); assertCondition(forbidden.includes("code-queue-task-interrupt"), "task interrupt must require approval", forbidden); assertCondition(forbidden.includes("code-queue-task-cancel"), "task cancel must require approval", forbidden); const alwaysForbidden = asStringArray(safety.alwaysForbidden, "alwaysForbidden"); assertCondition(alwaysForbidden.includes("print-token-values"), "contract must forbid token output", alwaysForbidden); const plan = dataOf(runCli(["commander", "plan", "--dry-run", "--session-id", "primary"], 0)); assertCondition(plan.mutation === false, "plan must be non-mutating", plan); assertCondition(asRecord(asRecord(plan.processDiscovery, "processDiscovery").startPlan, "startPlan").enabled === false, "start plan must be disabled", plan); assertCondition(asRecord(plan.bridge, "bridge").mutation === false, "bridge plan must not open bridges", plan); assertCondition(asRecord(plan.traceSummary, "traceSummary").mutation === false, "trace summary plan must be non-mutating", plan); assertCondition(asRecord(plan.issueEntries, "issueEntries").mutation === false, "issue entry plan must be non-mutating", plan); const prCloseout = asRecord(plan.prCloseout, "prCloseout"); assertCondition(prCloseout.mutation === false, "PR closeout plan must be non-mutating", prCloseout); assertCondition(asRecord(prCloseout.runnerBoundary, "runnerBoundary").maySelfCloseOrMergeOrdinaryPrWithinTaskBoundary === true, "ordinary PR runner self-close/merge boundary must be explicit", prCloseout); assertCondition(asRecord(prCloseout.unideskCliBoundary, "unideskCliBoundary").mergeSupported === true, "UniDesk REST gh pr merge must be guarded and supported", prCloseout); assertCondition(asRecord(plan.claudeqqApproval, "claudeqqApproval").mutation === false, "approval plan must be non-mutating", plan); const planWithoutDryRun = dataOf(runCli(["commander", "plan"], 1)); assertCondition(planWithoutDryRun.error === "dry-run-required", "plan must require dry-run", planWithoutDryRun); const approval = dataOf(runCli([ "commander", "approval", "request", "--action", "code-queue-task-interrupt", "--task-id", "task-123", "--reason", "heartbeat expired", "--dry-run", ], 0)); assertCondition(approval.mutation === false, "approval request must be non-mutating", approval); assertCondition(approval.requiresExplicitUserApproval === true, "approval request must require explicit user approval", approval); const claudeqq = asRecord(approval.claudeqq, "claudeqq"); assertCondition(claudeqq.mutation === false, "ClaudeQQ preview must not send", claudeqq); assertCondition(claudeqq.sendImplemented === false, "ClaudeQQ send must not be implemented", claudeqq); const invalidApproval = dataOf(runCli(["commander", "approval", "request", "--action", "read-token-file", "--dry-run"], 1)); assertCondition(invalidApproval.error === "validation-failed", "unsupported approval action must fail validation", invalidApproval); const secretReasonResult = spawnSync("bun", [ "scripts/cli.ts", "commander", "approval", "request", "--action", "code-queue-backend-restart", "--reason", "token=ghp_1234567890abcdef", "--dry-run", ], { cwd: process.cwd(), encoding: "utf8", maxBuffer: 4 * 1024 * 1024, }); assertCondition(secretReasonResult.status === 0, "secret-like approval reason command should still return successfully", { stdout: secretReasonResult.stdout, stderr: secretReasonResult.stderr, }); assertCondition(!secretReasonResult.stdout.includes("ghp_1234567890abcdef"), "secret-like approval reason must be redacted from stdout", { stdout: secretReasonResult.stdout, }); assertCondition(secretReasonResult.stdout.includes(""), "redacted approval reason should disclose redaction marker", { stdout: secretReasonResult.stdout, }); const doc = readFileSync("docs/reference/host-codex-commander.md", "utf8"); for (const snippet of [ "本地 skeleton 阶段", "/health", "/api/commander/contract", ".state/commander/", "trace summary dry-run", "approval draft preview", "sendImplemented=false", ]) { assertCondition(doc.includes(snippet), `reference doc missing snippet: ${snippet}`); } process.stdout.write(`${JSON.stringify({ ok: true, checks: [ "commander contract exposes host Codex service boundary and phase-one no-live-operation flags", "dry-run plan covers process discovery, SSH/PTY/stdio bridge, prompt guidance, trace summary, #20/#46 and ClaudeQQ approval", "non-dry-run plan is rejected", "approval request is dry-run only and rejects unsupported high-risk actions", "secret-like approval reasons are redacted from stdout", "reference doc states backend restart, task interrupt/cancel, and token-output prohibitions", ], }, null, 2)}\n`);