fix: tighten code queue pr preflight contract

This commit is contained in:
Codex
2026-05-21 06:03:06 +00:00
parent 62a2eaad68
commit 12a440f9d3
6 changed files with 114 additions and 10 deletions
@@ -11,7 +11,8 @@ function asRecord(value: unknown): JsonRecord {
return value as JsonRecord;
}
function fixtureRuntimePreflight(tokenPresent: boolean): JsonRecord {
function fixtureRuntimePreflight(tokenPresent: boolean, overrides: { systemGhPresent?: boolean } = {}): JsonRecord {
const systemGhPresent = overrides.systemGhPresent ?? true;
return {
ok: tokenPresent,
checkedAt: "2026-05-20T00:00:00.000Z",
@@ -26,12 +27,17 @@ function fixtureRuntimePreflight(tokenPresent: boolean): JsonRecord {
checkedAt: "2026-05-20T00:00:00.000Z",
tools: {
git: { ok: true, path: "/usr/bin/git", version: "git version 2.43.0" },
gh: { ok: true, path: "/usr/bin/gh", version: "gh version 2.45.0" },
gh: { ok: systemGhPresent, path: systemGhPresent ? "/usr/bin/gh" : null, version: systemGhPresent ? "gh version 2.45.0" : null },
hub: { ok: false, path: null, version: null },
jq: { ok: true, path: "/usr/bin/jq", version: "jq-1.7" },
ssh: { ok: true, path: "/usr/bin/ssh", version: "OpenSSH_9.6" },
curl: { ok: true, path: "/usr/bin/curl", version: "curl 8.5.0" },
},
unideskGhCli: {
ok: true,
path: "/workspace/unidesk/scripts/cli.ts",
present: true,
},
credentials: {
ghTokenPresent: tokenPresent,
githubTokenPresent: false,
@@ -68,18 +74,18 @@ function fixtureRuntimePreflight(tokenPresent: boolean): JsonRecord {
},
prCreateDryRun: tokenPresent ? { command: "sh", args: ["-lc", "bun scripts/cli.ts gh pr create --dry-run"], ok: true, exitCode: 0, signal: null, error: null, stdout: "{\"ok\":true,\"data\":{\"dryRun\":true}}", stderr: "" } : undefined,
limitations: tokenPresent ? [] : ["GH_TOKEN/GITHUB_TOKEN is not present; gh cannot create PRs unless another gh credential store is mounted"],
risks: [],
risks: systemGhPresent ? [] : ["system gh binary is missing; UniDesk REST gh CLI remains the supported PR create/comment path when scripts/cli.ts and GH_TOKEN/GITHUB_TOKEN are available"],
},
};
}
function fixtureResponse(tokenPresent: boolean): JsonRecord {
function fixtureResponse(tokenPresent: boolean, overrides: { systemGhPresent?: boolean } = {}): JsonRecord {
return {
ok: true,
status: 200,
body: {
ok: true,
runtimePreflight: fixtureRuntimePreflight(tokenPresent),
runtimePreflight: fixtureRuntimePreflight(tokenPresent, overrides),
},
};
}
@@ -96,6 +102,7 @@ export function runCodeQueuePrPreflightContract(): JsonRecord {
{ observedPath },
);
assertCondition(asRecord(missing).ok === false, "missing token preflight should set top-level ok=false", missing);
assertCondition(asRecord(missing).runnerDisposition === "infra-blocked", "missing token preflight should expose root runnerDisposition", missing);
const missingPreflight = asRecord(asRecord(missing).preflight);
assertCondition(missingPreflight.ok === false, "missing token preflight should fail", missingPreflight);
assertCondition(missingPreflight.runnerDisposition === "infra-blocked", "missing token must be infra-blocked", missingPreflight);
@@ -110,10 +117,21 @@ export function runCodeQueuePrPreflightContract(): JsonRecord {
});
const readyPreflight = asRecord(asRecord(ready).preflight);
assertCondition(asRecord(ready).ok === true, "token-ready preflight should set top-level ok=true", ready);
assertCondition(asRecord(ready).runnerDisposition === "ready", "token-ready preflight should expose root runnerDisposition", ready);
assertCondition(readyPreflight.ok === true, "token-ready preflight should pass fixture", readyPreflight);
assertCondition(readyPreflight.runnerDisposition === "ready", "ready preflight should report ready disposition", readyPreflight);
const readyTokenCoverage = asRecord(readyPreflight.tokenCoverage);
assertCondition(readyTokenCoverage.source === "GH_TOKEN", "ready token source should be redacted to key name only", readyTokenCoverage);
const readyContract = asRecord(readyPreflight.prCapabilityContract);
assertCondition(readyContract.targetBranch === "master", "PR preflight should expose target branch", readyContract);
const readyPushDryRun = asRecord(readyContract.pushDryRun);
assertCondition(readyPushDryRun.requested === true && readyPushDryRun.writesRemote === false, "push dry-run contract should be explicit and non-writing", readyPushDryRun);
const readyHandoff = asRecord(readyContract.expectedPrHandoff);
assertCondition(readyHandoff.targetBranch === "master" && readyHandoff.commanderReviewsAndMerges === true && readyHandoff.preflightCreatesPr === false, "PR handoff should stop at runner PR creation evidence", readyHandoff);
const readyMergeBoundary = asRecord(readyContract.unsupportedMergeBoundary);
assertCondition(readyMergeBoundary.degradedReason === "unsupported-command" && readyMergeBoundary.runnerDisposition === "business-failed", "merge boundary should remain unsupported", readyMergeBoundary);
const readyCliStatus = asRecord(readyContract.unideskGhCli);
assertCondition(readyCliStatus.ok === true && readyCliStatus.requiresSystemGhBinary === false, "UniDesk gh CLI availability should be separate from system gh binary", readyCliStatus);
const prCreateDryRun = codexPrPreflightQueryForTest(["--remote", "--pr-create-dry-run", "--pr-create-dry-run-head", "codequeue/pr-probe"], (path) => {
assertCondition(path === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&prCreateDryRun=1&prCreateDryRunHead=codequeue%2Fpr-probe", "PR create dry-run options should map to query string", { path });
@@ -122,6 +140,19 @@ export function runCodeQueuePrPreflightContract(): JsonRecord {
const prCreateDryRunPreflight = asRecord(asRecord(prCreateDryRun).preflight);
const prCreateDryRunProbe = asRecord(prCreateDryRunPreflight.prCreateDryRun);
assertCondition(prCreateDryRunProbe.ok === true, "PR create dry-run probe should be compacted", prCreateDryRunProbe);
const prCreateDryRunContract = asRecord(prCreateDryRunPreflight.prCapabilityContract);
const prCreateDryRunSummary = asRecord(prCreateDryRunContract.prCreateDryRun);
assertCondition(prCreateDryRunSummary.requested === true && prCreateDryRunSummary.headBranch === "codequeue/pr-probe" && prCreateDryRunSummary.writesRemote === false, "PR create dry-run contract should be explicit and non-writing", prCreateDryRunSummary);
const noSystemGh = codexPrPreflightQueryForTest(["--remote"], (path) => {
assertCondition(path === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1", "system gh missing fixture should use remote preflight path", { path });
return fixtureResponse(true, { systemGhPresent: false });
});
const noSystemGhPreflight = asRecord(asRecord(noSystemGh).preflight);
assertCondition(noSystemGhPreflight.ok === true, "missing system gh should not block UniDesk REST PR CLI when token and scripts/cli.ts exist", noSystemGhPreflight);
const noSystemGhTools = asRecord(noSystemGhPreflight.tools);
assertCondition(asRecord(noSystemGhTools.systemGhBinary).ok === false, "system gh binary status should be explicit", noSystemGhTools);
assertCondition(asRecord(noSystemGhTools.unideskGhCli).ok === true, "UniDesk gh CLI should stay available when system gh is missing", noSystemGhTools);
return {
ok: true,
@@ -129,8 +160,12 @@ export function runCodeQueuePrPreflightContract(): JsonRecord {
"stable runtime-preflight proxy path",
"missing GitHub token is infra-blocked",
"token key names are reported without values",
"fake token source reports only the env key",
"system gh binary and UniDesk REST gh CLI are distinct",
"push dry-run options are forwarded",
"PR create dry-run options are forwarded",
"dry-run push and PR create are marked non-writing",
"expected PR handoff and unsupported merge boundary are explicit",
],
};
}
+52
View File
@@ -2265,6 +2265,17 @@ function compactToolStatus(value: unknown): Record<string, unknown> {
};
}
function compactUniDeskGhCliStatus(value: unknown): Record<string, unknown> {
const cli = asRecord(value) ?? {};
return {
ok: cli.ok ?? false,
path: cli.path ?? null,
present: cli.present ?? false,
role: "repo-native REST GitHub CLI used by bun scripts/cli.ts gh",
requiresSystemGhBinary: false,
};
}
function compactAgentPortStatus(value: unknown): Record<string, unknown> {
const port = asRecord(value) ?? {};
return {
@@ -2298,6 +2309,7 @@ function tokenCoverageStatus(credentials: Record<string, unknown>): Record<strin
function compactPrRuntimePreflight(preflight: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
const pull = asRecord(preflight.pullRequestDelivery) ?? {};
const tools = asRecord(pull.tools) ?? {};
const unideskGhCli = compactUniDeskGhCliStatus(pull.unideskGhCli);
const credentials = asRecord(pull.credentials) ?? {};
const git = asRecord(pull.git) ?? {};
const githubContext = asRecord(pull.githubContext) ?? {};
@@ -2309,6 +2321,10 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
const limitations = Array.isArray(pull.limitations) ? pull.limitations.map(String) : [];
const risks = Array.isArray(pull.risks) ? pull.risks.map(String) : [];
const ok = preflight.ok === true && tokenCoverage.ok === true;
const defaultPushDryRunRef = "refs/heads/probe/code-queue-pr-capability-dryrun";
const pushDryRunRef = options.pushDryRunRef ?? defaultPushDryRunRef;
const targetBranch = "master";
const expectedHeadBranch = options.prCreateDryRunHead ?? (typeof git.branch === "string" && git.branch.length > 0 ? git.branch : "<head-branch>");
const result: Record<string, unknown> = {
ok,
checkedAt: preflight.checkedAt ?? pull.checkedAt ?? null,
@@ -2320,13 +2336,48 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
pid: preflight.pid ?? null,
},
tokenCoverage,
prCapabilityContract: {
targetBranch,
tokenSource: tokenCoverage.source,
systemGhBinaryRequiredForWrites: false,
unideskGhCli,
pushDryRun: {
requested: options.pushDryRun,
ref: pushDryRunRef,
writesRemote: false,
commandShape: `git push --dry-run origin HEAD:${pushDryRunRef}`,
},
prCreateDryRun: {
requested: options.prCreateDryRun,
headBranch: expectedHeadBranch,
writesRemote: false,
commandShape: `bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --base ${targetBranch} --head ${expectedHeadBranch} --dry-run`,
},
expectedPrHandoff: {
sourceBranch: expectedHeadBranch,
targetBranch,
runnerCreatesPrAfterAuthorization: true,
commanderReviewsAndMerges: true,
preflightCreatesPr: false,
preflightMergesPr: false,
},
unsupportedMergeBoundary: {
supported: false,
command: "bun scripts/cli.ts gh pr merge <number> --repo pikasTech/unidesk",
degradedReason: "unsupported-command",
runnerDisposition: "business-failed",
note: "UniDesk CLI intentionally does not merge PRs in this phase; runner handoff stops at PR creation and evidence.",
},
},
tools: {
git: compactToolStatus(tools.git),
gh: compactToolStatus(tools.gh),
systemGhBinary: compactToolStatus(tools.gh),
hub: compactToolStatus(tools.hub),
jq: compactToolStatus(tools.jq),
ssh: compactToolStatus(tools.ssh),
curl: compactToolStatus(tools.curl),
unideskGhCli,
},
agentPorts: {
codex: compactAgentPortStatus(ports.codex),
@@ -2404,6 +2455,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], fetcher: CodexResponseF
const compact = compactPrRuntimePreflight(preflight, options);
return {
ok: compact.ok,
runnerDisposition: compact.runnerDisposition,
upstream: response.upstream,
preflight: compact,
};