fix: split github contracts from script check
This commit is contained in:
@@ -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");
|
||||
@@ -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",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user