fix: split github contracts from script check

This commit is contained in:
Codex
2026-06-10 14:36:00 +00:00
parent 420a9a9e85
commit 4ee3a67089
12 changed files with 233 additions and 94 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、`websocket cl
1. 在 master `~/.codex/` 准备带后缀的上游 profile 文件,例如 `config.toml.<profile>``auth.json.<profile>`;禁止覆盖默认 `config.toml` / `auth.json`
2.`config/platform-infra/sub2api-codex-pool.yaml` 添加 `profiles.entries` 项,指定 `profile``accountName``configFile``authFile`
3. 如需要,给该项加 `priority``capacity``loadFactor``tempUnschedulable``openaiResponsesWebSocketsV2Mode``upstreamUserAgent`capacity/loadFactor 的具体数值只写在 YAML。
4. 如果新增账号会提高声明 capacity 总和,同步提高 `pool.minOwnerConcurrency``codex-pool plan` 会拒绝 owner concurrency 低于总 capacity 的配置
4. 如果新增账号会提高声明 capacity 总和,默认让省略的 `pool.minOwnerConcurrency` 继续按 capacity 总和自动解析;只有 YAML 已经显式写了该 override 时,才同步提高到不低于总 capacity,或删除 override 回到自动解析
5.`codex-pool plan`,确认 profile 可读、`base_url` 和 API key 来源有效,且 stdout 未泄露完整 key。
6.`codex-pool sync --confirm`
7.`codex-pool validate`
+1 -1
View File
@@ -209,7 +209,7 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
- `bun scripts/cli.ts help`:输出所有可用命令的 JSON 索引,详细规范见 `docs/reference/cli.md`
- `bun scripts/cli.ts --main-server-ip <ip> <command>`:默认通过公网 frontend 登录态远程执行调试、用户服务(底层命令名 `microservice`)、Code Queue 查询与节点自测命令,不要求主 server SSH key,详细规范见 `docs/reference/cli.md`
- `bun scripts/cli.ts config show`:校验并展示根目录 `config.json`,配置来源规则见 `docs/reference/config.md`
- `bun scripts/cli.ts check [--full|--files|--scripts-typecheck|--components|--compose|--logs|--recovery-guardrails|--rust]` / `bun scripts/cli.ts check recovery-guardrails`:默认只运行轻量配置和 TypeScript 语法检查;`check recovery-guardrails` 只读低噪声报告 D601 reboot 后 k3s/Code Queue hostPath、`/proc/mounts`、CRI sandbox 和 ContainerCreating 风险;Rust backend-core 检查默认只能在 D601 CI/dev execution 中用 `UNIDESK_D601_RUST_CHECK=1` 开启,backend-core 主 server 上线受控编译例外不改变 `check --rust` guard,规则见 `docs/reference/dev-environment.md``docs/reference/devops-hygiene.md`
- `bun scripts/cli.ts check [--full|--files|--scripts-typecheck|--gh-contracts|--components|--compose|--logs|--recovery-guardrails|--rust]` / `bun scripts/cli.ts check recovery-guardrails`:默认只运行轻量配置和 TypeScript 语法检查;GitHub issue/PR live contract 必须显式用 `--gh-contracts``--full` 开启;`check recovery-guardrails` 只读低噪声报告 D601 reboot 后 k3s/Code Queue hostPath、`/proc/mounts`、CRI sandbox 和 ContainerCreating 风险;Rust backend-core 检查默认只能在 D601 CI/dev execution 中用 `UNIDESK_D601_RUST_CHECK=1` 开启,backend-core 主 server 上线受控编译例外不改变 `check --rust` guard,规则见 `docs/reference/dev-environment.md``docs/reference/devops-hygiene.md`
- `bun scripts/cli.ts server start`:以异步 job 启动 database、backend-core、frontend、provider-gateway、code-queue-mgr 和主 server 用户服务,部署规则见 `docs/reference/deployment.md`
- `bun scripts/cli.ts server status`:查询固定端口、swap 摘要、容器状态、健康检查和访问 URL,包含生产 frontend、dev frontend proxy 和 provider ingress,判定标准见 `docs/reference/deployment.md``docs/reference/dev-environment.md`
- `bun scripts/cli.ts server swap status|ensure [--path /swapfile] [--size 2GiB] [--dry-run]`:以 JSON 查看或幂等创建主 server swapfile`ensure` 输出 before/after、动作、持久化状态和 degraded/failed 详情,规则见 `docs/reference/deployment.md`
+1 -1
View File
@@ -26,7 +26,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动
- 每个 CLI 命名空间必须支持 `help``--help``-h` 并返回 JSON,不得为了打印帮助而访问 runtime 服务、拉起交互会话或执行长时任务。
- `--main-server-ip <ip> <command>` 默认通过公网 frontend 登录态调用主 server 的同源 API 代理,不要求计算节点持有主 server SSH key;显式提供 `--main-server-key``--main-server-transport ssh` 时才使用旧 SSH 传输。远程 frontend 传输下的 `ssh <route> ...` 必须复用同一套结构化 route parser,支持 `D601``G14`、host workspace、`D601:win``D601:win/c/test``D601:k3s``D601:k3s:<namespace>:<workload>` 这类定位路径;它不向调用容器下发 provider token,也不要求调用容器能解析 backend-core 内网 DNS。
- `config show` 读取并校验根目录 `config.json`,不从环境变量、默认值或隐藏文件静默补配置。
- `check` 默认只执行轻量配置校验、Bun 版本检查和 Bun Transpiler 语法解析(覆盖 CLI 入口、主要 `scripts/` 模块和核心组件入口,不做类型推导);关键文件存在性、`scripts/` TypeScript 类型检查、`src/components/` TypeScript 类型检查、Docker Compose config、日志轮转策略扫描和 D601 recovery guardrails 默认不启用,分别通过 `--files``--scripts-typecheck``--components``--compose``--logs``--recovery-guardrails` 开启,或用 `--full` 一次性开启。`check recovery-guardrails` 是同一诊断的低噪声直接入口,报告 malformed `/proc/mounts`、kubelet validation risk、stale CRI sandbox count、Code Queue worktree/symlink、Code Queue/MDTODO hostPath 和 `ContainerCreating` 分类;它不得重启 k3s、删除 CRI sandbox、修改 hostPath、deploy/rollout 或 prune/reset。`--rust` 只允许在 D601 CI/dev execution 中配合 `UNIDESK_D601_RUST_CHECK=1` 使用,长期规则见 `docs/reference/dev-environment.md``docs/reference/devops-hygiene.md`
- `check` 默认只执行轻量配置校验、Bun 版本检查和 Bun Transpiler 语法解析(覆盖 CLI 入口、主要 `scripts/` 模块和核心组件入口,不做类型推导);关键文件存在性、`scripts/` TypeScript 类型检查、GitHub CLI contract、`src/components/` TypeScript 类型检查、Docker Compose config、日志轮转策略扫描和 D601 recovery guardrails 默认不启用,分别通过 `--files``--scripts-typecheck``--gh-contracts``--components``--compose``--logs``--recovery-guardrails` 开启,或用 `--full` 一次性开启。`--scripts-typecheck` 只表示 scripts TypeScript 与本地脚本合同,不应触发 GitHub issue/PR live contract 这类可能受网络/API 时延影响的检查;需要验证 GitHub CLI 时显式加 `--gh-contracts`,并以该 profile 的结果判定 GitHub CLI 变更。长命令项必须在 stderr 输出 `unidesk.check.progress` JSON linesstdout 保持最终 JSON 结果,避免 post-task 或人工运行时长时间无可见进度。`check recovery-guardrails` 是同一诊断的低噪声直接入口,报告 malformed `/proc/mounts`、kubelet validation risk、stale CRI sandbox count、Code Queue worktree/symlink、Code Queue/MDTODO hostPath 和 `ContainerCreating` 分类;它不得重启 k3s、删除 CRI sandbox、修改 hostPath、deploy/rollout 或 prune/reset。`--rust` 只允许在 D601 CI/dev execution 中配合 `UNIDESK_D601_RUST_CHECK=1` 使用,长期规则见 `docs/reference/dev-environment.md``docs/reference/devops-hygiene.md`
- `server start` 创建异步 job,在后台执行 Docker 构建和启动;命令本身只负责返回 job id、日志路径和启动命令。
- `server stop` 创建异步 job,在后台停止固定 Compose project 中的全部 UniDesk 服务。
- `server status` 查询公开端口、受限宿主端口、内部端口、主机 swap 摘要、Compose 容器、core/frontend/dev-frontend/provider/database 健康检查和访问 URLD601 Code Queue 使用的 PostgreSQL/OA Event Flow host mapping 必须出现在受限宿主端口而不是无条件公开入口中。低内存主 server 上 `swap.warning` 非空时,先执行 `server swap status``server swap ensure`
@@ -0,0 +1,31 @@
import { strict as assert } from "node:assert";
import { parseCheckOptions, runChecks } from "./src/check";
const scriptsOnly = parseCheckOptions(["--scripts-typecheck"]);
assert.equal(scriptsOnly.scriptsTypecheck, true);
assert.equal(scriptsOnly.ghContracts, false);
const full = parseCheckOptions(["--full"]);
assert.equal(full.scriptsTypecheck, true);
assert.equal(full.ghContracts, true);
const explicit = parseCheckOptions(["--gh-contracts"]);
assert.equal(explicit.scriptsTypecheck, false);
assert.equal(explicit.ghContracts, true);
const minimalConfig = {
project: { name: "contract" },
runtime: "contract",
} as never;
const result = runChecks(minimalConfig, { ...scriptsOnly, scriptsTypecheck: false });
const issueGuard = result.items.find((item) => item.name === "gh:issue-guard-contract");
const prFiles = result.items.find((item) => item.name === "gh:pr-files-contract");
const pr = result.items.find((item) => item.name === "gh:pr-contract");
for (const item of [issueGuard, prFiles, pr]) {
assert.ok(item, "GitHub contract item should be visible in check output");
assert.equal(item?.ok, true);
assert.equal((item?.detail as { skipped?: boolean }).skipped, true);
}
console.log("check gh contract scope contract ok");
+30 -15
View File
@@ -49,6 +49,20 @@ function deterministicSteerId(taskId: string, prompt: string): string {
return `steer_${Bun.SHA256.hash(`unidesk-code-queue-steer:v1\0${taskId}\0${prompt}`, "hex").slice(0, 24)}`;
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; json: JsonRecord | null }, command: string): void {
assertCondition(result.status !== 0 && result.json?.ok === false, `${command} should be frozen`, result.json ?? { stdout: result.stdout, stderr: result.stderr });
const data = nestedRecord(result.json?.data, []);
assertCondition(data.ok === false, `${command} frozen payload should be ok=false`, data);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
assertCondition(data.command === command, `${command} frozen payload should identify the command`, data);
const replacement = nestedRecord(data, ["replacement"]);
assertCondition(String(replacement.sessionsSteer || "").includes("agentrun v01 sessions steer"), `${command} should point to AgentRun sessions steer`, replacement);
const legacy = nestedRecord(data, ["legacy"]);
assertCondition(legacy.noDoubleWrite === true, `${command} should document no double-write`, legacy);
}
function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
assertCondition(response.ok === true, "CLI dry-run should succeed", response);
const data = nestedRecord(response.data, []);
@@ -82,31 +96,29 @@ function assertReason(result: unknown, reason: string, status: number | null): v
export function runCodeQueueCliSteerContract(): JsonRecord {
const positional = runCli(["codex", "steer", "codex_test_task", "correct the running task", "--dry-run"]);
assertDryRunPrompt(positional.json ?? {}, "correct the running task");
assertLegacyFrozenWrite(positional, "codex steer");
assertCondition(String(positional.json?.command || "").includes("<prompt:redacted>"), "outer command should redact positional steer prompt", positional.json ?? {});
assertCondition(!String(positional.json?.command || "").includes("correct the running task"), "outer command must not echo positional steer prompt", positional.json ?? {});
const stdin = runCli(["codex", "steer", "codex_test_task", "--prompt-stdin", "--dry-run"], "stdin steer prompt\n");
assertDryRunPrompt(stdin.json ?? {}, "stdin steer prompt\n");
assertLegacyFrozenWrite(stdin, "codex steer");
assertCondition(!stdin.stdout.includes("stdin steer prompt"), "frozen steer must not echo stdin prompt", { stdout: stdin.stdout });
const promptFile = join(tmpdir(), `unidesk-code-queue-steer-${process.pid}.txt`);
writeFileSync(promptFile, "file steer prompt", "utf8");
try {
const fromFile = runCli(["codex", "steer", "codex_test_task", "--prompt-file", promptFile, "--dry-run"]);
assertDryRunPrompt(fromFile.json ?? {}, "file steer prompt");
assertLegacyFrozenWrite(fromFile, "codex steer");
assertCondition(!fromFile.stdout.includes("file steer prompt"), "frozen steer must not echo file prompt", { stdout: fromFile.stdout });
} finally {
unlinkSync(promptFile);
}
const duplicateSource = runCli(["codex", "steer", "codex_test_task", "positional", "--prompt-stdin", "--dry-run"], "stdin\n");
assertCondition(duplicateSource.status !== 0, "duplicate prompt source should fail", duplicateSource.json ?? { stdout: duplicateSource.stdout });
const duplicateMessage = String(nestedRecord(duplicateSource.json, ["error"]).message || "");
assertCondition(duplicateMessage.includes("exactly one prompt source"), "duplicate prompt source error should be explicit", { duplicateMessage });
assertLegacyFrozenWrite(duplicateSource, "codex steer");
const unknownOption = runCli(["codex", "steer", "codex_test_task", "--queue", "default", "prompt", "--dry-run"]);
assertCondition(unknownOption.status !== 0, "unknown steer option should fail", unknownOption.json ?? { stdout: unknownOption.stdout });
const unknownMessage = String(nestedRecord(unknownOption.json, ["error"]).message || "");
assertCondition(unknownMessage.includes("unsupported codex steer option: --queue"), "unknown option error should name option", { unknownMessage });
assertLegacyFrozenWrite(unknownOption, "codex steer");
const help = runCli(["codex", "help"]);
assertCondition(help.status === 0 && help.json?.ok === true, "codex help should succeed", help.json ?? { stdout: help.stdout });
@@ -118,7 +130,10 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
assertCondition(!("error" in (advertisedConfirm.json ?? {})), "advertised steer-confirm command must not fall through to top-level error", advertisedConfirm.json ?? {});
const advertisedDeliveryStatus = String(nestedRecord(advertisedConfirm.json?.data, ["delivery"]).status || "");
assertCondition(["confirmed", "pending", "unknown", "not-supported"].includes(advertisedDeliveryStatus), "advertised steer-confirm command should return structured delivery status", { advertisedDeliveryStatus, json: advertisedConfirm.json });
assertCondition(!String(JSON.stringify(advertisedConfirm.json)).includes("\"message\":\"not found\""), "advertised steer-confirm command must not return top-level not found", advertisedConfirm.json ?? {});
if (advertisedDeliveryStatus === "not-supported") {
const diagnostics = nestedRecord(advertisedConfirm.json?.data, ["diagnostics"]);
assertCondition(diagnostics.reason === "steer-confirmation-endpoint-not-supported", "unsupported steer-confirm should use structured diagnostics", diagnostics);
}
let dryRunFetchCount = 0;
const dryRunDirect = codexSteerTaskForTest("direct_task", ["do not send", "--dry-run"], () => {
@@ -501,11 +516,11 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
return {
ok: true,
checks: [
"steer positional dry-run",
"steer stdin dry-run",
"steer prompt-file dry-run",
"duplicate prompt source failure",
"unsupported option failure",
"legacy steer positional dry-run is frozen",
"legacy steer stdin dry-run is frozen",
"legacy steer prompt-file dry-run is frozen",
"legacy steer duplicate prompt source is frozen",
"legacy steer unsupported option is frozen",
"codex help lists steer",
"advertised steer-confirm CLI command returns structured status",
"outer command redacts positional steer prompt",
@@ -45,6 +45,21 @@ function stringArray(value: unknown): string[] {
return Array.isArray(value) ? value.map((item) => String(item)) : [];
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; json: JsonRecord | null }, command: string): void {
assertCondition(result.status !== 0 && result.json?.ok === false, `${command} should be frozen`, result.json ?? { stdout: result.stdout, stderr: result.stderr });
const data = nestedRecord(result.json?.data, []);
assertCondition(data.ok === false, `${command} frozen payload should be ok=false`, data);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
assertCondition(data.command === command, `${command} frozen payload should identify the command`, data);
const replacement = nestedRecord(data, ["replacement"]);
assertCondition(String(replacement.queueSubmit || "").includes("agentrun v01 queue submit"), `${command} should point to AgentRun queue submit`, replacement);
assertCondition(String(replacement.sessionsSteer || "").includes("agentrun v01 sessions steer"), `${command} should point to AgentRun sessions steer`, replacement);
const legacy = nestedRecord(data, ["legacy"]);
assertCondition(legacy.noDoubleWrite === true, `${command} should document no double-write`, legacy);
}
function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
assertCondition(response.ok === true, "submit dry-run should succeed", response);
const data = nestedRecord(response.data, []);
@@ -66,8 +81,8 @@ export function runCodeQueueCliSubmitPromptContract(): JsonRecord {
].join("\n");
const stdin = runCli(["codex", "submit", "--prompt-stdin", "--queue", "prompt-contract", "--dry-run"], multilinePrompt);
assertDryRunPrompt(stdin.json ?? {}, multilinePrompt);
assertCondition(String(stdin.json?.command || "") === "codex submit --prompt-stdin --queue prompt-contract --dry-run", "stdin command should list flags without echoing prompt", stdin.json ?? {});
assertLegacyFrozenWrite(stdin, "codex submit");
assertCondition(!stdin.stdout.includes(multilinePrompt), "frozen submit must not echo stdin prompt", { stdout: stdin.stdout });
const tmp = mkdtempSync(join(tmpdir(), "unidesk-code-queue-submit-"));
const promptFile = join(tmp, "prompt.md");
@@ -75,22 +90,19 @@ export function runCodeQueueCliSubmitPromptContract(): JsonRecord {
writeFileSync(promptFile, filePrompt, "utf8");
try {
const fromFile = runCli(["codex", "submit", "--prompt-file", promptFile, "--queue", "prompt-contract", "--dry-run"]);
assertDryRunPrompt(fromFile.json ?? {}, filePrompt);
assertCondition(String(fromFile.json?.command || "").includes(`--prompt-file ${promptFile}`), "prompt-file command should retain file path for review", fromFile.json ?? {});
assertCondition(!String(fromFile.json?.command || "").includes("file prompt tail"), "prompt-file command must not echo file prompt text", fromFile.json ?? {});
assertLegacyFrozenWrite(fromFile, "codex submit");
assertCondition(!fromFile.stdout.includes("file prompt tail"), "frozen submit must not echo file prompt text", { stdout: fromFile.stdout });
} finally {
rmSync(tmp, { recursive: true, force: true });
}
const positional = runCli(["codex", "submit", "short smoke prompt", "--dry-run"]);
assertDryRunPrompt(positional.json ?? {}, "short smoke prompt");
assertLegacyFrozenWrite(positional, "codex submit");
assertCondition(String(positional.json?.command || "").includes("<prompt:redacted>"), "outer command should redact positional submit prompt", positional.json ?? {});
assertCondition(!String(positional.json?.command || "").includes("short smoke prompt"), "outer command must not echo positional submit prompt", positional.json ?? {});
const duplicateSource = runCli(["codex", "submit", "positional", "--prompt-stdin", "--dry-run"], "stdin\n");
assertCondition(duplicateSource.status !== 0, "duplicate prompt source should fail", duplicateSource.json ?? { stdout: duplicateSource.stdout });
const duplicateMessage = String(nestedRecord(duplicateSource.json, ["error"]).message || "");
assertCondition(duplicateMessage.includes("exactly one prompt source"), "duplicate prompt source error should be explicit", { duplicateMessage });
assertLegacyFrozenWrite(duplicateSource, "codex submit");
const longSubmittedPrompt = `${multilinePrompt}${"submitted prompt body must not be echoed\n".repeat(80)}`;
const submitSuccess = compactSubmitSuccessResponseForTest({
@@ -128,23 +140,23 @@ export function runCodeQueueCliSubmitPromptContract(): JsonRecord {
const promptInput = nestedRecord(data, ["promptInput"]);
const recommended = stringArray(promptInput.recommended);
const examples = nestedRecord(data, ["examples"]);
assertCondition(usage.some((line) => line.includes("--prompt-stdin")), "help usage should include --prompt-stdin", { usage });
assertCondition(usage.some((line) => line.includes("--prompt-file")), "help usage should include --prompt-file", { usage });
assertCondition(usage.some((line) => line.includes("cat <<'PROMPT'")), "help usage should include a quoted heredoc example", { usage });
const submitSummary = nestedRecord(data, ["submitSummary"]);
assertCondition(usage.some((line) => line.includes("codex submit # frozen legacy write entry")), "help usage should document frozen legacy submit", { usage });
assertCondition(recommended.includes("--prompt-stdin") && recommended.includes("--prompt-file"), "help should recommend stdin and file prompt sources", promptInput);
assertCondition(String(promptInput.sourceRule || "").includes("Exactly one prompt source"), "help should document exact prompt source rule", promptInput);
assertCondition(stringArray(examples.stdin).some((line) => line.includes("--prompt-stdin")), "help examples should include stdin command", examples);
assertCondition(String(examples.file || "").includes("--prompt-file"), "help examples should include file command", examples);
assertCondition(String(submitSummary.default || "").includes("legacy-code-queue-frozen"), "help submit summary should document frozen reason", submitSummary);
assertCondition(String(submitSummary.replacement || "").includes("agentrun v01 queue submit"), "help should point new submissions at AgentRun", submitSummary);
assertCondition(String(examples.agentRunSubmit || "").includes("agentrun v01 queue submit"), "help examples should include AgentRun submit", examples);
return {
ok: true,
checks: [
"submit --prompt-stdin preserves multiline quotes and newlines",
"submit --prompt-file preserves reviewed file contents",
"legacy submit --prompt-stdin is frozen and does not echo prompt text",
"legacy submit --prompt-file is frozen and does not echo prompt text",
"submit positional prompt is redacted from the outer command envelope",
"duplicate submit prompt source fails explicitly",
"legacy submit duplicate prompt sources still fail at the frozen boundary",
"submit success confirms write without echoing prompt",
"codex submit help documents stdin/file recommendations and copyable examples",
"codex submit help documents frozen legacy submit and AgentRun replacement",
],
};
}
@@ -27,6 +27,17 @@ function stringArray(value: unknown): string[] {
return Array.isArray(value) ? value.map((item) => String(item)) : [];
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; json: JsonRecord | null }, command: string): void {
assertCondition(result.status !== 0 && result.json?.ok === false, `${command} should be frozen`, result.json ?? { stdout: result.stdout, stderr: result.stderr });
const data = nestedRecord(result.json?.data, []);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
const replacement = nestedRecord(data, ["replacement"]);
assertCondition(String(replacement.queueSubmit || "").includes("agentrun v01 queue submit"), `${command} should point to AgentRun queue submit`, replacement);
assertCondition(String(replacement.sessionsSteer || "").includes("agentrun v01 sessions steer"), `${command} should point to AgentRun sessions steer`, replacement);
}
function runCli(args: string[], stdin?: string): { status: number | null; stdout: string; stderr: string; json: JsonRecord | null } {
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
cwd: process.cwd(),
@@ -148,15 +159,12 @@ export function runCodeQueuePromptLintContract(): JsonRecord {
}
const submitDryRun = runCli(["codex", "submit", "--prompt-stdin", "--dry-run"], unclassifiedM3SmokePrompt);
assertCondition(submitDryRun.status === 0 && submitDryRun.json?.ok === true, "submit dry-run should still succeed for commander review", submitDryRun.json ?? { stdout: submitDryRun.stdout });
const submitPromptLint = nestedRecord(submitDryRun.json?.data, ["promptLint"]);
assertCondition(submitPromptLint.dispatchDisposition === "needs-authorization", "submit dry-run should embed prompt lint authorization blocker", submitPromptLint);
assertCondition(submitPromptLint.requiredClass === "live-mutating", "submit dry-run lint should require live-mutating", submitPromptLint);
assertLegacyFrozenWrite(submitDryRun, "codex submit");
assertCondition(!submitDryRun.stdout.includes("res_boxsimu_1"), "frozen submit must not echo prompt text", { stdout: submitDryRun.stdout });
const steerDryRun = runCli(["codex", "steer", "codex_test_task", "--prompt-stdin", "--dry-run"], unclassifiedM3SmokePrompt);
assertCondition(steerDryRun.status === 0 && steerDryRun.json?.ok === true, "steer dry-run should succeed for commander review", steerDryRun.json ?? { stdout: steerDryRun.stdout });
const steerPromptLint = nestedRecord(steerDryRun.json?.data, ["promptLint"]);
assertCondition(steerPromptLint.dispatchDisposition === "needs-authorization", "steer dry-run should embed prompt lint authorization blocker", steerPromptLint);
assertLegacyFrozenWrite(steerDryRun, "codex steer");
assertCondition(!steerDryRun.stdout.includes("res_boxsimu_1"), "frozen steer must not echo prompt text", { stdout: steerDryRun.stdout });
const help = runCli(["codex", "help"]);
assertCondition(help.status === 0 && help.json?.ok === true, "codex help should succeed", help.json ?? { stdout: help.stdout });
@@ -174,8 +182,8 @@ export function runCodeQueuePromptLintContract(): JsonRecord {
"unclassified HWLAB M3 smoke defaults read-only but requires live-mutating authorization",
"prompt-lint evidence redacts secret-looking values",
"prompt-lint CLI is dry-run, non-mutating, and does not echo full prompt text",
"submit --dry-run embeds prompt live-authorization lint",
"steer --dry-run embeds prompt live-authorization lint",
"legacy submit --dry-run is frozen and points to AgentRun",
"legacy steer --dry-run is frozen and points to AgentRun",
"codex help documents prompt-lint and authorization classes",
],
};
+21 -6
View File
@@ -82,6 +82,20 @@ function deterministicResumeId(taskId: string, prompt: string): string {
return `resume_${Bun.SHA256.hash(`unidesk-code-queue-resume:v1\0${taskId}\0${prompt}`, "hex").slice(0, 24)}`;
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; json: JsonRecord | null }, command: string): void {
assertCondition(result.status !== 0 && result.json?.ok === false, `${command} should be frozen`, result.json ?? { stdout: result.stdout, stderr: result.stderr });
const data = nestedRecord(result.json?.data, []);
assertCondition(data.ok === false, `${command} frozen payload should be ok=false`, data);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
assertCondition(data.command === command, `${command} frozen payload should identify the command`, data);
const replacement = nestedRecord(data, ["replacement"]);
assertCondition(String(replacement.sessionsSteer || "").includes("agentrun v01 sessions steer"), `${command} should point to AgentRun sessions steer`, replacement);
const legacy = nestedRecord(data, ["legacy"]);
assertCondition(legacy.noDoubleWrite === true, `${command} should document no double-write`, legacy);
}
function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
assertCondition(response.ok === true, "CLI dry-run should succeed", response);
const data = nestedRecord(response.data, []);
@@ -100,25 +114,26 @@ function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
export function runCodeQueueResumeContract(): JsonRecord {
const positional = runCli(["codex", "resume", "codex_test_task", "fix the PR conflict", "--dry-run"]);
assertDryRunPrompt(positional.json ?? {}, "fix the PR conflict");
assertLegacyFrozenWrite(positional, "codex resume");
assertCondition(String(positional.json?.command || "").includes("<prompt:redacted>"), "outer command should redact positional resume prompt", positional.json ?? {});
assertCondition(!String(positional.json?.command || "").includes("fix the PR conflict"), "outer command must not echo positional resume prompt", positional.json ?? {});
const stdin = runCli(["codex", "resume", "codex_test_task", "--prompt-stdin", "--dry-run"], "stdin resume prompt\n");
assertDryRunPrompt(stdin.json ?? {}, "stdin resume prompt\n");
assertLegacyFrozenWrite(stdin, "codex resume");
assertCondition(!stdin.stdout.includes("stdin resume prompt"), "frozen resume must not echo stdin prompt", { stdout: stdin.stdout });
const promptFile = join(tmpdir(), `unidesk-code-queue-resume-${process.pid}.txt`);
writeFileSync(promptFile, "file resume prompt", "utf8");
try {
const fromFile = runCli(["codex", "resume", "codex_test_task", "--prompt-file", promptFile, "--dry-run"]);
assertDryRunPrompt(fromFile.json ?? {}, "file resume prompt");
assertLegacyFrozenWrite(fromFile, "codex resume");
assertCondition(!fromFile.stdout.includes("file resume prompt"), "frozen resume must not echo file prompt", { stdout: fromFile.stdout });
} finally {
unlinkSync(promptFile);
}
const duplicateSource = runCli(["codex", "resume", "codex_test_task", "positional", "--prompt-stdin", "--dry-run"], "stdin\n");
assertCondition(duplicateSource.status !== 0, "duplicate prompt source should fail", duplicateSource.json ?? { stdout: duplicateSource.stdout });
assertCondition(String(nestedRecord(duplicateSource.json, ["error"]).message || "").includes("exactly one prompt source"), "duplicate prompt source error should be explicit", duplicateSource.json ?? {});
assertLegacyFrozenWrite(duplicateSource, "codex resume");
const help = runCli(["codex", "help"]);
const usage = Array.isArray(nestedRecord(help.json?.data, []).usage) ? nestedRecord(help.json?.data, []).usage as unknown[] : [];
@@ -291,7 +306,7 @@ export function runCodeQueueResumeContract(): JsonRecord {
return {
ok: true,
checks: [
"resume positional/stdin/file dry-runs",
"legacy resume positional/stdin/file dry-runs are frozen",
"bounded disclosure and outer command redaction",
"non-dry-run sends resumeId and omits prompt from output",
"terminal resume accepted with thread reuse metadata",
@@ -54,6 +54,19 @@ function assertSecretFree(output: string): void {
}
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; json: JsonRecord | null }, command: string): void {
assertCondition(result.status !== 0 && result.json?.ok === false, `${command} should be frozen`, result.json ?? { stdout: result.stdout, stderr: result.stderr });
const data = nestedRecord(result.json?.data, []);
assertCondition(data.ok === false, `${command} frozen payload should be ok=false`, data);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
const replacement = nestedRecord(data, ["replacement"]);
assertCondition(String(replacement.queueSubmit || "").includes("agentrun v01 queue submit"), `${command} should point to AgentRun queue submit`, replacement);
const legacy = nestedRecord(data, ["legacy"]);
assertCondition(legacy.noDoubleWrite === true, `${command} should document no double-write`, legacy);
}
export function runCodeQueueSubmitExecutionModeContract(): JsonRecord {
assertCondition(normalizeRequestedCodeExecutionMode("full-access") === "full-access", "shared parser should preserve short requested mode ids");
assertCondition(normalizeCodeExecutionMode("full-access") === "default", "shared execution-mode normalizer should keep full-access on effective default");
@@ -61,33 +74,12 @@ export function runCodeQueueSubmitExecutionModeContract(): JsonRecord {
assertCondition(requestedCodeExecutionModeIsRecognized("default") === true, "shared recognition helper should accept default mode");
const defaultMode = runCli(["codex", "submit", "execution mode default smoke", "--dry-run"]);
assertCondition(defaultMode.status === 0 && defaultMode.json?.ok === true, "default submit dry-run should succeed", defaultMode.json ?? { stdout: defaultMode.stdout, stderr: defaultMode.stderr });
assertLegacyFrozenWrite(defaultMode, "codex submit");
assertSecretFree(defaultMode.stdout);
const defaultData = nestedRecord(defaultMode.json?.data, []);
const defaultRequest = nestedRecord(defaultData, ["request"]);
const defaultExecutionMode = nestedRecord(defaultData, ["executionMode"]);
const defaultPermissions = nestedRecord(defaultData, ["runnerPermissions"]);
assertCondition(defaultRequest.executionMode === undefined, "default payload should omit executionMode so service default is authoritative", defaultRequest);
assertCondition(defaultExecutionMode.requested === null, "default mode should show no explicit requested mode", defaultExecutionMode);
assertCondition(defaultExecutionMode.effective === "default", "default mode should expose effective default", defaultExecutionMode);
assertCondition(defaultExecutionMode.normalized === false, "default mode should not be reported as normalized", defaultExecutionMode);
assertCondition(defaultExecutionMode.recognized === true, "default mode should be recognized", defaultExecutionMode);
assertCondition(defaultPermissions.observed === false && defaultPermissions.perTaskOverrideSupported === false, "dry-run should mark runner permissions unobserved and non per-task", defaultPermissions);
const fullAccess = runCli(["codex", "submit", "execution mode full access smoke", "--execution-mode", "full-access", "--dry-run"]);
assertCondition(fullAccess.status === 0 && fullAccess.json?.ok === true, "full-access submit dry-run should succeed", fullAccess.json ?? { stdout: fullAccess.stdout, stderr: fullAccess.stderr });
assertLegacyFrozenWrite(fullAccess, "codex submit");
assertSecretFree(fullAccess.stdout);
const fullData = nestedRecord(fullAccess.json?.data, []);
const fullRequest = nestedRecord(fullData, ["request"]);
const fullExecutionMode = nestedRecord(fullData, ["executionMode"]);
assertCondition(fullRequest.executionMode === "full-access", "payload should preserve the requested executionMode value for backend visibility", fullRequest);
assertCondition(fullExecutionMode.requested === "full-access", "full-access request should be visible", fullExecutionMode);
assertCondition(fullExecutionMode.effective === "default", "full-access should normalize to the effective default runtime mode", fullExecutionMode);
assertCondition(fullExecutionMode.recognized === false, "full-access should not be treated as a recognized Code Queue execution mode", fullExecutionMode);
assertCondition(fullExecutionMode.normalized === true, "full-access should explicitly show normalization", fullExecutionMode);
assertCondition(fullExecutionMode.requestedLooksLikeSandbox === true, "full-access should be classified as a sandbox-like request", fullExecutionMode);
assertCondition(String(fullExecutionMode.permissionBoundary || "").includes("runnerPermissions.sandbox"), "permission boundary should point at runnerPermissions.sandbox", fullExecutionMode);
assertCondition(String(fullExecutionMode.warning || "").includes("not applied"), "full-access warning should say it is not a per-task sandbox override", fullExecutionMode);
const promptText = "submitted full-access prompt body must stay omitted";
const submitted = compactSubmitSuccessResponseForTest({
@@ -138,11 +130,11 @@ export function runCodeQueueSubmitExecutionModeContract(): JsonRecord {
return {
ok: true,
checks: [
"default codex submit dry-run omits executionMode, reports effective default, and marks runner permissions unobserved",
"--execution-mode full-access preserves requested mode, reports effective default, and warns that sandbox permissions are service-level",
"legacy codex submit dry-run is frozen and points to AgentRun",
"legacy --execution-mode full-access submit dry-run is frozen without printing credentials",
"real submit summary fixture exposes requested/effective mode plus observed runnerPermissions without prompt echo",
"shared execution-mode helpers preserve requested full-access while normalizing effective runtime to default",
"execution-mode dry-run output does not print credential assignments",
"execution-mode frozen output does not print credential assignments",
],
};
}
@@ -43,6 +43,16 @@ function dataOf(envelope: JsonRecord): JsonRecord {
return asRecord(envelope.data, "data");
}
function assertLegacyFrozenWrite(result: { status: number | null; stdout: string; stderr: string; envelope: JsonRecord }, command: string): void {
assertCondition(result.status !== 0 && result.envelope.ok === false, `${command} should be frozen`, result.envelope);
const data = dataOf(result.envelope);
assertCondition(data.frozen === true, `${command} frozen payload should expose frozen=true`, data);
assertCondition(data.mutation === false, `${command} frozen payload should be non-mutating`, data);
assertCondition(data.degradedReason === "legacy-code-queue-frozen", `${command} should use the legacy frozen reason`, data);
const replacement = asRecord(data.replacement, "replacement");
assertCondition(String(replacement.queueSubmit || "").includes("agentrun v01 queue submit"), `${command} should point to AgentRun queue submit`, replacement);
}
const completePrompt = `
UniDesk#20 #118 / commander prompt boundary lint
@@ -116,9 +126,8 @@ try {
}
const submitDryRun = runCli(["codex", "submit", "--prompt-stdin", "--queue", "prompt-lint-contract", "--dry-run"], incompletePromptWithSecret);
assertCondition(submitDryRun.status === 0 && submitDryRun.envelope.ok === true, "codex submit --dry-run should not be gated by commander prompt-lint", submitDryRun.envelope);
const submitData = dataOf(submitDryRun.envelope);
assertCondition(asRecord(submitData.request, "submit request").prompt !== undefined, "submit dry-run should keep its existing prompt review behavior", submitData);
assertLegacyFrozenWrite(submitDryRun, "codex submit");
assertCondition(!submitDryRun.stdout.includes("ghp_prompt_lint_contract_secret"), "frozen submit must not echo prompt secret", submitDryRun.stdout);
const helpRun = runCli(["commander", "--help"]);
assertCondition(helpRun.status === 0 && helpRun.envelope.ok === true, "commander help should succeed", helpRun.envelope);
@@ -143,7 +152,7 @@ process.stdout.write(`${JSON.stringify({
"missing PR/artifact/DEV rollout/ROLLOUT_OK/PROD-secret-DB-rollback clauses are reported as high risk",
"prompt-lint supports --prompt-file and --stdin",
"prompt-lint output does not echo full prompt or secret-like prompt text",
"commander prompt-lint remains advisory and does not gate codex submit --dry-run",
"commander prompt-lint remains advisory while legacy codex submit stays frozen",
"commander help and host commander reference document the advisory lint entry",
],
}, null, 2)}\n`);
@@ -14,7 +14,7 @@ const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as {
minOwnerConcurrency?: number;
defaultTempUnschedulable?: {
enabled?: boolean;
rules?: Array<{ statusCode?: number; keywords?: string[]; durationMinutes?: number }>;
rules?: Array<{ statusCode?: number; keywords?: string[]; durationMinutes?: number; description?: string }>;
};
};
profiles?: { entries?: Array<{ profile?: string; accountName?: string; capacity?: number; loadFactor?: number; openaiResponsesWebSocketsV2Mode?: string | null }> };
@@ -28,6 +28,8 @@ const defaultPriority = parsed.pool?.defaultAccountPriority ?? 0;
const defaultCapacity = parsed.pool?.defaultAccountCapacity ?? 0;
const defaultLoadFactor = parsed.pool?.defaultAccountLoadFactor ?? 0;
const desiredCapacity = entries.reduce((total, entry) => total + (entry.capacity ?? defaultCapacity), 0);
const explicitMinOwnerConcurrency = parsed.pool?.minOwnerConcurrency;
const resolvedMinOwnerConcurrency = explicitMinOwnerConcurrency ?? desiredCapacity;
const allowedWebSocketModes = new Set(["off", "ctx_pool", "passthrough"]);
const wsEnabledEntries = entries.filter((entry) => entry.openaiResponsesWebSocketsV2Mode && entry.openaiResponsesWebSocketsV2Mode !== "off");
const localWsEnabled = parsed.localCodex?.supportsWebSockets === true || parsed.localCodex?.responsesWebSocketsV2 === true;
@@ -56,7 +58,21 @@ if (localWsEnabled) {
} else {
assertCondition(wsEnabledEntries.length === 0, "local Codex WebSocket transport disabled means all account WSv2 capability declarations must be off or omitted", { localCodex: parsed.localCodex, wsEnabledEntries });
}
assertCondition((parsed.pool?.minOwnerConcurrency ?? 0) >= desiredCapacity, "pool owner concurrency must not bottleneck the declared account capacity set", { minOwnerConcurrency: parsed.pool?.minOwnerConcurrency, desiredCapacity });
assertCondition(
explicitMinOwnerConcurrency === undefined || (Number.isInteger(explicitMinOwnerConcurrency) && explicitMinOwnerConcurrency > 0),
"explicit pool owner concurrency override must be a positive integer when declared",
{ minOwnerConcurrency: explicitMinOwnerConcurrency },
);
assertCondition(
resolvedMinOwnerConcurrency >= desiredCapacity,
"pool owner concurrency must auto-resolve or be configured to cover the declared account capacity set",
{
minOwnerConcurrency: explicitMinOwnerConcurrency,
minOwnerConcurrencySource: explicitMinOwnerConcurrency === undefined ? "auto-capacity-sum" : "yaml",
resolvedMinOwnerConcurrency,
desiredCapacity,
},
);
if (parsed.pool?.defaultTempUnschedulable?.enabled === true) {
assertCondition(rules.length > 0, "enabled temporary unschedulable policy must declare rules", parsed.pool?.defaultTempUnschedulable);
assertCondition(rules.every((rule) => Number.isInteger(rule.statusCode) && (rule.statusCode ?? 0) >= 100 && (rule.statusCode ?? 0) <= 599), "temporary unschedulable rules must declare valid HTTP status codes", rules);
@@ -116,7 +132,7 @@ console.log(JSON.stringify({
ok: true,
checks: [
"routing config is schema-valid without profile-specific test gates",
"pool owner concurrency covers the YAML account capacity set",
"pool owner concurrency auto-resolves or covers the YAML account capacity set",
"profile load factor overrides are YAML-controlled positive integers",
"public Caddy response-header timeout is long enough for Codex compact",
"optional WebSocket mode overrides use supported values",
+51 -10
View File
@@ -19,6 +19,7 @@ const syntaxFiles = [
"scripts/platform-infra-sub2api-codex-routing-contract-test.ts",
"scripts/platform-infra-sub2api-codex-temp-unsched-contract-test.ts",
"scripts/platform-infra-sub2api-http-upstream-contract-test.ts",
"scripts/check-gh-contract-scope-contract-test.ts",
"scripts/src/playwright-cli.ts",
"scripts/src/check.ts",
"scripts/src/artifact-registry.ts",
@@ -86,6 +87,7 @@ export interface CheckOptions {
compose: boolean;
logs: boolean;
recoveryGuardrails: boolean;
ghContracts: boolean;
rust: boolean;
}
@@ -97,6 +99,7 @@ const defaultCheckOptions: CheckOptions = {
compose: false,
logs: false,
recoveryGuardrails: false,
ghContracts: false,
rust: false,
};
@@ -105,15 +108,16 @@ export function checkHelp(): Record<string, unknown> {
ok: true,
command: "check",
usage: [
"bun scripts/cli.ts check [--syntax-only|--full|--files|--scripts-typecheck|--components|--compose|--logs|--recovery-guardrails|--rust]",
"bun scripts/cli.ts check [--syntax-only|--full|--files|--scripts-typecheck|--gh-contracts|--components|--compose|--logs|--recovery-guardrails|--rust]",
"bun scripts/cli.ts check recovery-guardrails",
],
defaultMode: "syntax/config only; Rust is never compiled on the master server by default",
options: [
{ name: "--syntax-only|--basic", description: "Run only config validation, Bun version and TypeScript syntax transpile." },
{ name: "--full", description: "Enable all local checks except Rust compilation." },
{ name: "--full", description: "Enable all non-Rust checks, including explicit GitHub CLI contracts." },
{ name: "--files", description: "Verify required entrypoint files, including backend-core Cargo files." },
{ name: "--scripts-typecheck", description: "Run scripts TypeScript typecheck." },
{ name: "--gh-contracts", description: "Run slower GitHub CLI contract tests; intentionally separate from generic scripts typecheck." },
{ name: "--components", description: "Run component TypeScript typecheck." },
{ name: "--compose", description: "Render Docker Compose config." },
{ name: "--logs", description: "Check unified log rotation policy." },
@@ -143,10 +147,13 @@ export function parseCheckOptions(args: string[]): CheckOptions {
options.compose = true;
options.logs = true;
options.recoveryGuardrails = true;
options.ghContracts = true;
} else if (arg === "--files") {
options.files = true;
} else if (arg === "--scripts-typecheck") {
options.scriptsTypecheck = true;
} else if (arg === "--gh-contracts") {
options.ghContracts = true;
} else if (arg === "--components") {
options.components = true;
} else if (arg === "--compose") {
@@ -171,13 +178,29 @@ function fileItem(path: string): CheckItem {
return { name: `file:${path}`, ok: existsSync(absolute), detail: absolute };
}
function emitCheckProgress(detail: Record<string, unknown>): void {
console.error(JSON.stringify({ event: "unidesk.check.progress", ...detail }));
}
function commandItem(name: string, command: string[], timeoutMs = 30_000, env: NodeJS.ProcessEnv = process.env): CheckItem {
const startedAt = Date.now();
emitCheckProgress({ phase: "started", name, command, timeoutMs });
const result = runCommand(command, repoRoot, { timeoutMs, env });
const durationMs = Date.now() - startedAt;
emitCheckProgress({
phase: result.exitCode === 0 ? "succeeded" : "failed",
name,
durationMs,
exitCode: result.exitCode,
signal: result.signal,
timedOut: result.timedOut,
});
return {
name,
ok: result.exitCode === 0,
detail: {
command,
durationMs,
exitCode: result.exitCode,
signal: result.signal,
timedOut: result.timedOut,
@@ -312,7 +335,7 @@ export function runRecoveryGuardrailsCheck(config: UniDeskConfig): ReturnType<ty
return compactD601RecoveryGuardrails(runD601RecoveryGuardrails(config));
}
export function runChecks(config: UniDeskConfig, options: CheckOptions = defaultCheckOptions): { ok: boolean; mode: string; options: CheckOptions; items: CheckItem[] } {
export function runChecks(config: UniDeskConfig, options: CheckOptions = defaultCheckOptions): { ok: boolean; mode: string; options: CheckOptions; summary: { total: number; passed: number; failed: number; failedItems: string[] }; items: CheckItem[] } {
const items: CheckItem[] = [
{ name: "config:validated", ok: true, detail: { project: config.project.name, runtime: config.runtime } },
commandItem("bun:version", ["bun", "--version"]),
@@ -382,6 +405,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/deploy-artifact-matrix-contract-test.ts"),
fileItem("scripts/decision-center-desired-state-contract-test.ts"),
fileItem("scripts/code-queue-prompt-observation-test.ts"),
fileItem("scripts/check-gh-contract-scope-contract-test.ts"),
fileItem("scripts/gh-cli-issue-guard-contract-test.ts"),
fileItem("scripts/gh-cli-pr-files-contract-test.ts"),
fileItem("scripts/gh-cli-pr-contract-test.ts"),
@@ -449,9 +473,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("artifact-registry:direct-compose-dry-run-matrix", ["bun", "scripts/artifact-consumer-dry-run-matrix-test.ts"], 30_000));
items.push(commandItem("schedule:cli-contract", ["bun", "scripts/schedule-cli-contract-test.ts"], 30_000));
items.push(commandItem("server:cleanup-plan-contract", ["bun", "scripts/server-cleanup-plan-contract-test.ts"], 30_000));
items.push(commandItem("gh:issue-guard-contract", ["bun", "scripts/gh-cli-issue-guard-contract-test.ts"], 30_000));
items.push(commandItem("gh:pr-files-contract", ["bun", "scripts/gh-cli-pr-files-contract-test.ts"], 30_000));
items.push(commandItem("gh:pr-contract", ["bun", "scripts/gh-cli-pr-contract-test.ts"], 30_000));
items.push(commandItem("check:gh-contract-scope-contract", ["bun", "scripts/check-gh-contract-scope-contract-test.ts"], 30_000));
items.push(commandItem("playwright:cli-wrapper-contract", ["bun", "scripts/playwright-cli-contract-test.ts"], 30_000));
items.push(commandItem("platform-infra:sub2api-codex-local-config-contract", ["bun", "scripts/platform-infra-sub2api-codex-local-config-contract-test.ts"], 30_000));
items.push(commandItem("platform-infra:sub2api-codex-routing-contract", ["bun", "scripts/platform-infra-sub2api-codex-routing-contract-test.ts"], 30_000));
@@ -494,9 +516,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("artifact-registry:direct-compose-dry-run-matrix", "main-server direct artifact consumer dry-run matrix is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("schedule:cli-contract", "Schedule CLI contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("server:cleanup-plan-contract", "Server cleanup dry-run contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("gh:issue-guard-contract", "GitHub issue CLI contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("gh:pr-files-contract", "GitHub PR files/stat contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("gh:pr-contract", "GitHub PR CLI contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("check:gh-contract-scope-contract", "Check option scope contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("playwright:cli-wrapper-contract", "Playwright wrapper/headless/session contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("auth-broker:p0-contract", "Auth Broker P0 skeleton and CLI adapter contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("d601:recovery-guardrails-contract", "D601 recovery guardrails fixture contract is opt-in with script checks", "--scripts-typecheck or --full"));
@@ -507,6 +527,15 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
} else {
items.push(skippedItem("logs:unified-hourly-rotation", "policy scan is opt-in", "--logs or --full"));
}
if (options.ghContracts) {
items.push(commandItem("gh:issue-guard-contract", ["bun", "scripts/gh-cli-issue-guard-contract-test.ts"], 30_000));
items.push(commandItem("gh:pr-files-contract", ["bun", "scripts/gh-cli-pr-files-contract-test.ts"], 30_000));
items.push(commandItem("gh:pr-contract", ["bun", "scripts/gh-cli-pr-contract-test.ts"], 30_000));
} else {
items.push(skippedItem("gh:issue-guard-contract", "GitHub issue CLI contract is opt-in because it can be slower than generic scripts typecheck", "--gh-contracts or --full"));
items.push(skippedItem("gh:pr-files-contract", "GitHub PR files/stat contract is opt-in because it can be slower than generic scripts typecheck", "--gh-contracts or --full"));
items.push(skippedItem("gh:pr-contract", "GitHub PR CLI contract is opt-in because it can be slower than generic scripts typecheck", "--gh-contracts or --full"));
}
if (options.recoveryGuardrails) {
const recovery = runRecoveryGuardrailsCheck(config);
items.push({
@@ -545,5 +574,17 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
} else {
items.push(skippedItem("rust:backend-core", "Rust check/build must run through an approved native k3s CI artifact publication, not on the master server or CD runtime target", "--rust inside native k3s CI with UNIDESK_NATIVE_K3S_RUST_CHECK=1"));
}
return { ok: items.every((item) => item.ok), mode: options.full ? "full" : "basic", options, items };
const failedItems = items.filter((item) => !item.ok).map((item) => item.name);
return {
ok: failedItems.length === 0,
mode: options.full ? "full" : "basic",
options,
summary: {
total: items.length,
passed: items.length - failedItems.length,
failed: failedItems.length,
failedItems,
},
items,
};
}