760 lines
40 KiB
TypeScript
760 lines
40 KiB
TypeScript
import { codexPrPreflightQueryForTest } from "./src/code-queue";
|
|
import type { UniDeskConfig } from "./src/config";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function asRecord(value: unknown): JsonRecord {
|
|
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), "expected JSON object", { value });
|
|
return value as JsonRecord;
|
|
}
|
|
|
|
function localBackendCoreMissingFixture(): JsonRecord {
|
|
return {
|
|
ok: false,
|
|
failureKind: "target-stack-not-running",
|
|
degradedReason: "backend-core-container-missing",
|
|
runnerDisposition: "infra-blocked",
|
|
message: "backend-core/database target containers are not running; only verify-only containers were observed.",
|
|
targetStack: {
|
|
expectedContainers: ["unidesk-backend-core", "unidesk-database", "baidu-netdisk-backend"],
|
|
missingContainers: ["unidesk-backend-core", "unidesk-database", "baidu-netdisk-backend"],
|
|
verifyOnlyObserved: true,
|
|
},
|
|
readOnlyCommands: [
|
|
"bun scripts/cli.ts server status",
|
|
"bun scripts/cli.ts schedule list",
|
|
"bun scripts/cli.ts schedule runs --limit 20",
|
|
],
|
|
authorizationRequiredForRecovery: ["restore runtime secret coverage", "start the target stack"],
|
|
};
|
|
}
|
|
|
|
function remoteControlPlaneResult(overrides: Partial<JsonRecord> = {}): JsonRecord {
|
|
return {
|
|
ok: true,
|
|
runnerDisposition: "ready",
|
|
failureKind: null,
|
|
degradedReason: null,
|
|
upstream: { ok: true, status: 200 },
|
|
controlPlane: {
|
|
mode: "remote-frontend",
|
|
host: "74.48.78.17",
|
|
frontendUrl: "http://74.48.78.17:18081",
|
|
localBackendCoreMissing: true,
|
|
remoteFallbackUsed: true,
|
|
},
|
|
preflight: {
|
|
ok: true,
|
|
runnerDisposition: "ready",
|
|
failureKind: null,
|
|
degradedReason: null,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
runner: {
|
|
serviceId: "code-queue",
|
|
plane: "D601 k3s scheduler/runner",
|
|
queueScope: "all queues executed by the scheduler, including default",
|
|
cwd: "/workspace/unidesk",
|
|
pid: 123,
|
|
},
|
|
tokenCoverage: {
|
|
ok: true,
|
|
source: "GH_TOKEN",
|
|
ghTokenPresent: true,
|
|
githubTokenPresent: false,
|
|
ghCredentialStorePresent: false,
|
|
runnerDisposition: "ready",
|
|
missing: [],
|
|
scope: "scheduler-runner-env",
|
|
},
|
|
authBroker: {
|
|
ok: true,
|
|
source: "GH_TOKEN",
|
|
needed: false,
|
|
configured: false,
|
|
runnerDisposition: "ready",
|
|
failureKind: null,
|
|
degradedReason: null,
|
|
runnerEnvTokenRequiredWithoutBroker: true,
|
|
brokerCredentialSource: null,
|
|
valuesPrinted: false,
|
|
evidence: {
|
|
envTokenMissing: false,
|
|
missing: [],
|
|
systemGhBinaryOk: true,
|
|
systemGhBinaryRequiredForWrites: false,
|
|
unideskGhCliObserved: true,
|
|
unideskGhCliOk: true,
|
|
unideskGhCliRequiresSystemGhBinary: false,
|
|
systemGhMissingMisclassifiedAsUniDeskCliMissing: false,
|
|
},
|
|
next: [],
|
|
reference: "docs/reference/auth-broker.md#post-v1githubpr-preflight",
|
|
},
|
|
prCapabilityContract: {
|
|
targetBranch: "master",
|
|
tokenSource: "GH_TOKEN",
|
|
systemGhBinaryRequiredForWrites: false,
|
|
unideskGhCli: { ok: true, path: "/workspace/unidesk/scripts/cli.ts", present: true, role: "repo-native REST GitHub CLI used by bun scripts/cli.ts gh", requiresSystemGhBinary: false },
|
|
pushDryRun: { requested: false, ref: "refs/heads/probe/code-queue-pr-capability-dryrun", writesRemote: false, commandShape: "git push --dry-run origin HEAD:refs/heads/probe/code-queue-pr-capability-dryrun" },
|
|
prCreateDryRun: { requested: false, headBranch: "feature/code-queue-pr-preflight", writesRemote: false, commandShape: "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --base master --head feature/code-queue-pr-preflight --dry-run" },
|
|
expectedPrHandoff: {
|
|
sourceBranch: "feature/code-queue-pr-preflight",
|
|
targetBranch: "master",
|
|
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.",
|
|
},
|
|
},
|
|
controlPlane: {
|
|
mode: "local-backend-core",
|
|
localBackendCoreMissing: false,
|
|
remoteFallbackUsed: false,
|
|
},
|
|
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" },
|
|
systemGhBinary: { ok: true, path: "/usr/bin/gh", version: "gh version 2.45.0" },
|
|
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, role: "repo-native REST GitHub CLI used by bun scripts/cli.ts gh", requiresSystemGhBinary: false },
|
|
},
|
|
agentPorts: {
|
|
codex: { ok: true, commandPath: "/usr/local/bin/codex", version: "codex 0.128.0", errors: [] },
|
|
opencode: { ok: true, commandPath: "/usr/local/bin/opencode", version: "opencode 1.14.48", errors: [] },
|
|
},
|
|
git: {
|
|
insideWorktree: true,
|
|
branch: "feature/code-queue-pr-preflight",
|
|
head: "abc1234",
|
|
originMaster: "def5678",
|
|
remoteOrigin: "git@github.com:pikasTech/unidesk.git",
|
|
home: "/root",
|
|
homeWritable: true,
|
|
knownHostsPresent: true,
|
|
privateKeyPresent: true,
|
|
},
|
|
githubContext: {
|
|
host: "github.com",
|
|
apiBaseUrl: "https://api.github.com",
|
|
repo: "pikasTech/unidesk",
|
|
issueProbeNumber: 35,
|
|
},
|
|
egress: {
|
|
proxy: {
|
|
selectedProxyHost: "d601-provider-egress-proxy.unidesk.svc.cluster.local",
|
|
selectedProxyPort: "18789",
|
|
selectedProxyHostResolvable: true,
|
|
},
|
|
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
issueApi: null,
|
|
},
|
|
remote: {
|
|
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: true, exitCode: 0, signal: null, error: null, stdout: "abc1234\trefs/heads/master\n", stderr: "" },
|
|
gitHttpsLsRemote: null,
|
|
githubSshAuthenticated: true,
|
|
ghAuthStatus: { command: "gh", args: ["auth", "status"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghRepoView: { command: "gh", args: ["repo", "view", "pikasTech/unidesk"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghIssueView: { command: "gh", args: ["issue", "view", "35"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghPrReadOnly: { command: "gh", args: ["pr", "list"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
},
|
|
pushDryRun: null,
|
|
prCreateDryRun: null,
|
|
limitations: [],
|
|
risks: [],
|
|
runnerDisposition: "ready",
|
|
recoveryHint: "Runner PR workflow has env-token coverage for the scheduler.",
|
|
commands: {
|
|
local: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
|
runner: "bun scripts/cli.ts codex pr-preflight --remote",
|
|
runnerPushDryRun: "bun scripts/cli.ts codex pr-preflight --remote --push-dry-run --push-dry-run-ref refs/heads/probe/code-queue-pr-capability",
|
|
runnerPrCreateDryRun: "bun scripts/cli.ts codex pr-preflight --remote --pr-create-dry-run --pr-create-dry-run-head <head-branch>",
|
|
rawProxy: "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight?remote=1 --raw",
|
|
},
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function rawRuntimePreflightFixture(overrides: Partial<JsonRecord> = {}): JsonRecord {
|
|
return {
|
|
ok: true,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
cwd: "/workspace/unidesk",
|
|
pid: 123,
|
|
pullRequestDelivery: {
|
|
ok: true,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
tools: {
|
|
git: { ok: true, path: "/usr/bin/git", version: "git version 2.43.0" },
|
|
gh: { ok: false, path: null, version: 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: false,
|
|
githubTokenPresent: false,
|
|
ghHostsConfigPresent: false,
|
|
gitCredentialsPresent: false,
|
|
},
|
|
authBroker: {
|
|
ok: false,
|
|
configured: false,
|
|
source: "broker/auth-broker-needed",
|
|
endpointEnvKey: null,
|
|
runnerEnvTokenRequired: false,
|
|
credentialSource: null,
|
|
failureKind: "auth-missing",
|
|
degradedReason: "auth-broker-needed",
|
|
capability: {
|
|
source: "missing-token",
|
|
githubRestAuth: false,
|
|
operations: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"],
|
|
systemGhBinaryRequiredForWrites: false,
|
|
preflightWritesRemote: false,
|
|
preflightCreatesPr: false,
|
|
preflightMergesPr: false,
|
|
realPrCreateRequiresCommanderAuthorization: true,
|
|
valuesPrinted: false,
|
|
},
|
|
nextAction: "configure-auth-broker-or-env-token",
|
|
next: ["configure UNIDESK_AUTH_BROKER_URL or AUTH_BROKER_URL for broker-backed runner auth"],
|
|
valuesRead: false,
|
|
valuesPrinted: false,
|
|
},
|
|
git: {
|
|
insideWorktree: true,
|
|
branch: "code-queue/issue-35-pr-dry-run-probe",
|
|
head: "abc1234",
|
|
originMaster: "def5678",
|
|
remoteOrigin: "git@github.com:pikasTech/unidesk.git",
|
|
home: "/root",
|
|
homeWritable: true,
|
|
knownHostsPresent: true,
|
|
privateKeyPresent: true,
|
|
},
|
|
githubContext: {
|
|
host: "github.com",
|
|
apiBaseUrl: "https://api.github.com",
|
|
repo: "pikasTech/unidesk",
|
|
issueProbeNumber: 20,
|
|
},
|
|
egress: {
|
|
proxy: {
|
|
selectedProxyHost: "d601-provider-egress-proxy.unidesk.svc.cluster.local",
|
|
selectedProxyPort: "18789",
|
|
selectedProxyHostResolvable: true,
|
|
},
|
|
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
issueApi: null,
|
|
},
|
|
remote: {
|
|
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: true, exitCode: 0, signal: null, error: null, stdout: "abc1234\trefs/heads/master\n", stderr: "" },
|
|
gitHttpsLsRemote: null,
|
|
githubSshAuthenticated: true,
|
|
ghAuthStatus: null,
|
|
ghRepoView: null,
|
|
ghIssueView: null,
|
|
ghPrReadOnly: null,
|
|
},
|
|
limitations: [],
|
|
risks: [
|
|
"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 or auth-broker are available",
|
|
],
|
|
},
|
|
ports: {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
let observedLocalPath = "";
|
|
const remoteFallback = await codexPrPreflightQueryForTest(["--remote", "--issue", "35"], {
|
|
config: {
|
|
project: { name: "unidesk", timezone: "Etc/UTC" },
|
|
runtime: { typescript: "bun", bunVersion: "1.3.13" },
|
|
network: {
|
|
host: "0.0.0.0",
|
|
publicHost: "74.48.78.17",
|
|
core: { port: 18080, containerPort: 8080 },
|
|
frontend: { port: 18081, containerPort: 8080 },
|
|
devFrontend: { port: 18083, containerPort: 8080 },
|
|
database: { port: 15432, containerPort: 5432 },
|
|
providerIngress: { port: 18082, containerPort: 8081 },
|
|
},
|
|
database: { user: "unidesk", password: "unidesk_dev_password", name: "unidesk", volume: "unidesk_pgdata_10gb", volumeSize: "15GB" },
|
|
providerGateway: {
|
|
id: "main-server",
|
|
name: "Main Server Provider",
|
|
token: "unidesk-dev-token-change-me",
|
|
labels: { host: "main-server", role: "self-provider", docker: true },
|
|
heartbeatIntervalMs: 15000,
|
|
reconnectBaseMs: 1000,
|
|
reconnectMaxMs: 30000,
|
|
metrics: { diskPath: "/" },
|
|
upgrade: { hostProjectRoot: "/root/unidesk", workspacePath: "/workspace", composeFile: "docker-compose.yml", composeEnvFile: ".state/docker-compose.env", composeProject: "unidesk", service: "provider-gateway", runnerImage: "unidesk_provider-gateway" },
|
|
},
|
|
docker: { composeFile: "docker-compose.yml", projectName: "unidesk" },
|
|
microservices: [],
|
|
paths: { stateDir: ".state", logsDir: "logs", docsReferenceDir: "docs/reference" },
|
|
sshForwarding: { mode: "ws", keyDir: "/root/.ssh", host: "main-server", port: 22, user: "root" },
|
|
auth: { username: "admin", password: "Liang6516.", sessionSecret: "secret", sessionTtlSeconds: 86400 },
|
|
},
|
|
coreFetch: (path) => {
|
|
observedLocalPath = path;
|
|
return localBackendCoreMissingFixture();
|
|
},
|
|
remoteMainServerPrPreflight: () => remoteControlPlaneResult({
|
|
controlPlane: {
|
|
mode: "remote-frontend",
|
|
host: "74.48.78.17",
|
|
frontendUrl: "http://74.48.78.17:18081",
|
|
localBackendCoreMissing: true,
|
|
remoteFallbackUsed: true,
|
|
},
|
|
failureKind: null,
|
|
degradedReason: null,
|
|
}),
|
|
});
|
|
assertCondition(observedLocalPath === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&issue=35", "runner-like local path should stay on the stable proxy", { observedLocalPath });
|
|
const fallback = asRecord(remoteFallback);
|
|
assertCondition(fallback.ok === true, "remote fallback should succeed", fallback);
|
|
assertCondition(fallback.runnerDisposition === "ready", "remote fallback should stay ready", fallback);
|
|
assertCondition(fallback.controlPlane && asRecord(fallback.controlPlane).remoteFallbackUsed === true, "remote fallback should be marked", fallback.controlPlane);
|
|
assertCondition(fallback.failureKind === null, "remote fallback should not invent a failure kind when remote control plane is healthy", fallback);
|
|
const fallbackPreflight = asRecord(fallback.preflight);
|
|
assertCondition(fallbackPreflight.ok === true, "remote fallback preflight should stay ready", fallbackPreflight);
|
|
assertCondition(asRecord(fallbackPreflight.tokenCoverage).source === "GH_TOKEN", "token source should be GH_TOKEN", fallbackPreflight.tokenCoverage);
|
|
assertCondition(asRecord(fallbackPreflight.prCapabilityContract).targetBranch === "master", "target branch should stay master", fallbackPreflight.prCapabilityContract);
|
|
|
|
const authMissing = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: null,
|
|
coreFetch: () => localBackendCoreMissingFixture(),
|
|
});
|
|
const remoteControlPlaneMissingRecord = asRecord(authMissing);
|
|
assertCondition(remoteControlPlaneMissingRecord.ok === false, "missing control plane should fail", remoteControlPlaneMissingRecord);
|
|
assertCondition(remoteControlPlaneMissingRecord.failureKind === "control-plane-missing", "missing control plane should classify as control-plane-missing", remoteControlPlaneMissingRecord);
|
|
assertCondition(remoteControlPlaneMissingRecord.degradedReason === "remote-control-plane-unreachable", "missing control plane should classify as remote-control-plane-unreachable", remoteControlPlaneMissingRecord);
|
|
assertCondition(asRecord(remoteControlPlaneMissingRecord.controlPlane).localBackendCoreMissing === true, "local backend-core absence should remain evidence only", remoteControlPlaneMissingRecord.controlPlane);
|
|
|
|
const directAuthMissing = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: { network: { publicHost: "74.48.78.17", frontend: { port: 18081 } } } as unknown as UniDeskConfig,
|
|
coreFetch: () => localBackendCoreMissingFixture(),
|
|
remoteMainServerPrPreflight: () => remoteControlPlaneResult({
|
|
ok: false,
|
|
failureKind: "auth-missing",
|
|
degradedReason: "GH_TOKEN/GITHUB_TOKEN missing",
|
|
runnerDisposition: "infra-blocked",
|
|
message: "GH_TOKEN/GITHUB_TOKEN missing in remote control plane",
|
|
tokenCoverage: {
|
|
ok: false,
|
|
source: null,
|
|
ghTokenPresent: false,
|
|
githubTokenPresent: false,
|
|
ghCredentialStorePresent: false,
|
|
runnerDisposition: "infra-blocked",
|
|
missing: ["GH_TOKEN", "GITHUB_TOKEN"],
|
|
scope: "scheduler-runner-env",
|
|
},
|
|
prCapabilityContract: {
|
|
targetBranch: "master",
|
|
tokenSource: null,
|
|
systemGhBinaryRequiredForWrites: false,
|
|
unideskGhCli: { ok: true, path: "/workspace/unidesk/scripts/cli.ts", present: true, role: "repo-native REST GitHub CLI used by bun scripts/cli.ts gh", requiresSystemGhBinary: false },
|
|
pushDryRun: { requested: false, ref: "refs/heads/probe/code-queue-pr-capability-dryrun", writesRemote: false, commandShape: "git push --dry-run origin HEAD:refs/heads/probe/code-queue-pr-capability-dryrun" },
|
|
prCreateDryRun: { requested: false, headBranch: "feature/code-queue-pr-preflight", writesRemote: false, commandShape: "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --base master --head feature/code-queue-pr-preflight --dry-run" },
|
|
expectedPrHandoff: { sourceBranch: "feature/code-queue-pr-preflight", targetBranch: "master", 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." },
|
|
},
|
|
}),
|
|
});
|
|
const directAuthMissingRecord = asRecord(directAuthMissing);
|
|
assertCondition(directAuthMissingRecord.ok === false, "auth-missing remote result should fail", directAuthMissingRecord);
|
|
assertCondition(directAuthMissingRecord.failureKind === "auth-missing", "missing token should classify as auth-missing", directAuthMissingRecord);
|
|
assertCondition(directAuthMissingRecord.degradedReason === "GH_TOKEN/GITHUB_TOKEN missing", "auth missing should state token gap", directAuthMissingRecord);
|
|
const directAuthScopeBoundary = asRecord(directAuthMissingRecord.scopeBoundary);
|
|
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerDevContainer);
|
|
assertCondition(directAuthScopeBoundary.scopesAreIndependent === true, "remote auth-missing must distinguish scheduler env from active runner dev container", directAuthScopeBoundary);
|
|
assertCondition(String(directAuthScopeBoundary.authMissingInterpretation ?? "").includes("do not simplify"), "remote auth-missing must warn against overbroad interpretation", directAuthScopeBoundary);
|
|
assertCondition(directAuthActiveRunner.notEquivalentToSchedulerEnv === true, "active runner token capability must be a separate scope", directAuthActiveRunner);
|
|
|
|
const gitRemoteGap = remoteControlPlaneResult({
|
|
ok: false,
|
|
failureKind: "git-remote-gap",
|
|
degradedReason: "git remote probe failed",
|
|
runnerDisposition: "infra-blocked",
|
|
message: "git ls-remote probe failed",
|
|
});
|
|
const gitRemoteGapRecord = asRecord(gitRemoteGap);
|
|
assertCondition(gitRemoteGapRecord.failureKind === "git-remote-gap", "git probe failures should stay structured", gitRemoteGapRecord);
|
|
|
|
const proxyGap = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: null,
|
|
coreFetch: () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
runtimePreflight: {
|
|
ok: false,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
cwd: "/workspace/unidesk",
|
|
pid: 123,
|
|
pullRequestDelivery: {
|
|
ok: false,
|
|
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" },
|
|
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: true,
|
|
githubTokenPresent: false,
|
|
ghHostsConfigPresent: false,
|
|
gitCredentialsPresent: false,
|
|
},
|
|
git: {
|
|
insideWorktree: true,
|
|
branch: "feature/code-queue-pr-preflight",
|
|
head: "abc1234",
|
|
originMaster: "def5678",
|
|
remoteOrigin: "git@github.com:pikasTech/unidesk.git",
|
|
home: "/root",
|
|
homeWritable: true,
|
|
knownHostsPresent: true,
|
|
privateKeyPresent: true,
|
|
},
|
|
githubContext: {
|
|
host: "github.com",
|
|
apiBaseUrl: "https://api.github.com",
|
|
repo: "pikasTech/unidesk",
|
|
issueProbeNumber: 20,
|
|
},
|
|
egress: {
|
|
proxy: {
|
|
selectedProxyHost: "missing-egress-proxy.unidesk.svc.cluster.local",
|
|
selectedProxyPort: "18789",
|
|
selectedProxyHostResolvable: false,
|
|
},
|
|
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "Could not resolve proxy" },
|
|
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "Could not resolve proxy" },
|
|
issueApi: null,
|
|
},
|
|
remote: {
|
|
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: true, exitCode: 0, signal: null, error: null, stdout: "abc1234\trefs/heads/master\n", stderr: "" },
|
|
gitHttpsLsRemote: null,
|
|
githubSshAuthenticated: true,
|
|
ghAuthStatus: { command: "gh", args: ["auth", "status"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghRepoView: { command: "gh", args: ["repo", "view", "pikasTech/unidesk"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghIssueView: { command: "gh", args: ["issue", "view", "20"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
ghPrReadOnly: { command: "gh", args: ["pr", "list"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
},
|
|
limitations: [
|
|
"configured GitHub egress proxy host is not resolvable: missing-egress-proxy.unidesk.svc.cluster.local",
|
|
"GitHub HTTPS probe failed with the default environment/proxy",
|
|
],
|
|
risks: [],
|
|
},
|
|
ports: {},
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
const proxyGapRecord = asRecord(proxyGap);
|
|
assertCondition(proxyGapRecord.failureKind === "proxy-gap", "proxy failures should classify as proxy-gap", proxyGapRecord);
|
|
assertCondition(proxyGapRecord.degradedReason === "configured GitHub egress proxy host is not resolvable: missing-egress-proxy.unidesk.svc.cluster.local", "proxy degraded reason should point at the proxy", proxyGapRecord);
|
|
|
|
let observedDryRunPath = "";
|
|
const dryRunContract = await codexPrPreflightQueryForTest([
|
|
"--remote",
|
|
"--push-dry-run",
|
|
"--push-dry-run-ref",
|
|
"refs/heads/probe/code-queue-pr-capability",
|
|
"--pr-create-dry-run",
|
|
"--pr-create-dry-run-head",
|
|
"code-queue/issue-35-pr-dry-run-probe",
|
|
"--issue",
|
|
"20",
|
|
], {
|
|
config: null,
|
|
coreFetch: (path) => {
|
|
observedDryRunPath = path;
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
runtimePreflight: {
|
|
ok: false,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
cwd: "/workspace/unidesk",
|
|
pid: 123,
|
|
pullRequestDelivery: {
|
|
ok: false,
|
|
checkedAt: "2026-05-20T00:00:00.000Z",
|
|
tools: {
|
|
git: { ok: true, path: "/usr/bin/git", version: "git version 2.43.0" },
|
|
gh: { ok: false, path: null, version: 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: false,
|
|
githubTokenPresent: false,
|
|
ghHostsConfigPresent: false,
|
|
gitCredentialsPresent: false,
|
|
},
|
|
git: {
|
|
insideWorktree: true,
|
|
branch: "code-queue/issue-35-pr-dry-run-probe",
|
|
head: "abc1234",
|
|
originMaster: "def5678",
|
|
remoteOrigin: "git@github.com:pikasTech/unidesk.git",
|
|
home: "/root",
|
|
homeWritable: true,
|
|
knownHostsPresent: true,
|
|
privateKeyPresent: false,
|
|
},
|
|
githubContext: {
|
|
host: "github.com",
|
|
apiBaseUrl: "https://api.github.com",
|
|
repo: "pikasTech/unidesk",
|
|
issueProbeNumber: 20,
|
|
},
|
|
egress: {
|
|
proxy: {
|
|
selectedProxyHost: "d601-provider-egress-proxy.unidesk.svc.cluster.local",
|
|
selectedProxyPort: "18789",
|
|
selectedProxyHostResolvable: true,
|
|
},
|
|
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: true, exitCode: 0, signal: null, error: null, stdout: "", stderr: "" },
|
|
issueApi: { command: "sh", args: ["-lc", "curl issue"], ok: false, exitCode: 1, signal: null, error: null, stdout: "http_status=404", stderr: "" },
|
|
},
|
|
remote: {
|
|
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: true, exitCode: 0, signal: null, error: null, stdout: "abc1234\trefs/heads/master\n", stderr: "" },
|
|
gitHttpsLsRemote: { command: "git", args: ["ls-remote", "--heads", "https://github.com/pikasTech/unidesk.git", "master"], ok: false, exitCode: 128, signal: null, error: null, stdout: "", stderr: "Authentication failed" },
|
|
githubSshAuthenticated: true,
|
|
ghAuthStatus: null,
|
|
ghRepoView: null,
|
|
ghIssueView: null,
|
|
ghPrReadOnly: null,
|
|
},
|
|
pushDryRun: { command: "git", args: ["push", "--dry-run", "origin", "HEAD:refs/heads/probe/code-queue-pr-capability"], ok: false, exitCode: 128, signal: null, error: null, stdout: "", stderr: "Permission denied" },
|
|
prCreateDryRun: { command: "sh", args: ["-lc", "bun scripts/cli.ts gh pr create --dry-run"], ok: false, exitCode: 1, signal: null, error: null, stdout: "", stderr: "GH_TOKEN/GITHUB_TOKEN missing" },
|
|
limitations: [
|
|
"GH_TOKEN/GITHUB_TOKEN is not present; gh cannot create PRs unless another gh credential store is mounted",
|
|
"git push --dry-run failed for probe branch",
|
|
"PR create dry-run body/command guard failed",
|
|
],
|
|
risks: [
|
|
"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",
|
|
],
|
|
},
|
|
ports: {},
|
|
},
|
|
},
|
|
};
|
|
},
|
|
});
|
|
assertCondition(observedDryRunPath === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&pushDryRun=1&pushDryRunRef=refs%2Fheads%2Fprobe%2Fcode-queue-pr-capability&prCreateDryRun=1&prCreateDryRunHead=code-queue%2Fissue-35-pr-dry-run-probe&issue=20", "combined dry-run query should pass all requested guards", { observedDryRunPath });
|
|
const dryRunRecord = asRecord(dryRunContract);
|
|
assertCondition(dryRunRecord.failureKind === "auth-missing", "missing runner token should remain auth-missing even when system gh is absent", dryRunRecord);
|
|
const dryRunPreflight = asRecord(dryRunRecord.preflight);
|
|
const dryRunAuthBroker = asRecord(dryRunPreflight.authBroker);
|
|
const dryRunScopeBoundary = asRecord(dryRunPreflight.scopeBoundary);
|
|
const dryRunActiveRunner = asRecord(dryRunPreflight.activeRunnerDevContainer);
|
|
assertCondition(dryRunAuthBroker.source === "broker/auth-broker-needed", "missing runner token should expose broker/auth-broker-needed", dryRunAuthBroker);
|
|
assertCondition(dryRunAuthBroker.degradedReason === "auth-broker-needed", "auth broker degraded reason should be explicit", dryRunAuthBroker);
|
|
assertCondition(dryRunScopeBoundary.scopesAreIndependent === true, "local compact preflight should expose independent auth scopes", dryRunScopeBoundary);
|
|
assertCondition(dryRunActiveRunner.scope === "current-cli-process", "local compact preflight should expose current CLI process capability", dryRunActiveRunner);
|
|
const dryRunBrokerEvidence = asRecord(dryRunAuthBroker.evidence);
|
|
assertCondition(dryRunBrokerEvidence.systemGhBinaryOk === false, "system gh absence should be reported separately", dryRunBrokerEvidence);
|
|
assertCondition(dryRunBrokerEvidence.unideskGhCliOk === true, "UniDesk REST gh CLI should not be marked unavailable because system gh is missing", dryRunBrokerEvidence);
|
|
assertCondition(dryRunBrokerEvidence.systemGhMissingMisclassifiedAsUniDeskCliMissing === false, "system gh absence must not be misclassified", dryRunBrokerEvidence);
|
|
const dryRunPrContract = asRecord(dryRunPreflight.prCapabilityContract);
|
|
assertCondition(asRecord(dryRunPrContract.pushDryRun).requested === true, "push dry-run should be requested", dryRunPrContract);
|
|
assertCondition(asRecord(dryRunPrContract.pushDryRun).writesRemote === false, "push dry-run must be marked non-writing", dryRunPrContract);
|
|
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).requested === true, "PR create dry-run should be requested", dryRunPrContract);
|
|
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).writesRemote === false, "PR create dry-run must be marked non-writing", dryRunPrContract);
|
|
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).headBranch === "code-queue/issue-35-pr-dry-run-probe", "PR dry-run head should come from the option", dryRunPrContract);
|
|
|
|
const brokerIssuedContract = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: null,
|
|
coreFetch: () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
runtimePreflight: rawRuntimePreflightFixture({
|
|
pullRequestDelivery: {
|
|
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
|
|
ok: true,
|
|
authBroker: {
|
|
ok: true,
|
|
configured: true,
|
|
source: "auth-broker",
|
|
endpointEnvKey: "UNIDESK_AUTH_BROKER_URL",
|
|
runnerEnvTokenRequired: false,
|
|
credentialSource: "broker-held-github-credential",
|
|
failureKind: null,
|
|
degradedReason: null,
|
|
capability: {
|
|
source: "broker-issued-token",
|
|
githubRestAuth: true,
|
|
operations: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"],
|
|
systemGhBinaryRequiredForWrites: false,
|
|
preflightWritesRemote: false,
|
|
preflightCreatesPr: false,
|
|
preflightMergesPr: false,
|
|
realPrCreateRequiresCommanderAuthorization: true,
|
|
valuesPrinted: false,
|
|
},
|
|
nextAction: "use-auth-broker",
|
|
next: ["keep PR preflight read-only; create a real PR only after commander authorization"],
|
|
valuesRead: false,
|
|
valuesPrinted: false,
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
}),
|
|
});
|
|
const brokerIssuedRecord = asRecord(brokerIssuedContract);
|
|
assertCondition(brokerIssuedRecord.ok === true, "broker-issued token branch should be ready", brokerIssuedRecord);
|
|
const brokerIssuedPreflight = asRecord(brokerIssuedRecord.preflight);
|
|
const brokerIssuedTokenCoverage = asRecord(brokerIssuedPreflight.tokenCoverage);
|
|
const brokerIssuedAuthBroker = asRecord(brokerIssuedPreflight.authBroker);
|
|
const brokerIssuedCapability = asRecord(brokerIssuedAuthBroker.capability);
|
|
const brokerIssuedPrContract = asRecord(brokerIssuedPreflight.prCapabilityContract);
|
|
assertCondition(brokerIssuedTokenCoverage.source === "auth-broker", "broker-issued branch should use auth-broker token coverage", brokerIssuedTokenCoverage);
|
|
assertCondition(brokerIssuedTokenCoverage.credentialSource === "broker-issued-token", "broker-issued branch should expose broker-issued-token capability", brokerIssuedTokenCoverage);
|
|
assertCondition(brokerIssuedAuthBroker.source === "auth-broker", "broker-issued branch should expose authBroker.source", brokerIssuedAuthBroker);
|
|
assertCondition(brokerIssuedAuthBroker.nextAction === "use-auth-broker", "broker-issued branch should expose nextAction", brokerIssuedAuthBroker);
|
|
assertCondition(brokerIssuedCapability.systemGhBinaryRequiredForWrites === false, "broker-issued branch should not require system gh binary", brokerIssuedCapability);
|
|
assertCondition(brokerIssuedCapability.realPrCreateRequiresCommanderAuthorization === true, "real PR creation should still require commander authorization", brokerIssuedCapability);
|
|
assertCondition(asRecord(brokerIssuedPrContract.authBroker).source === "auth-broker", "PR capability should include broker source", brokerIssuedPrContract);
|
|
|
|
const envTokenContract = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: null,
|
|
coreFetch: () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
runtimePreflight: rawRuntimePreflightFixture({
|
|
pullRequestDelivery: {
|
|
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
|
|
ok: true,
|
|
credentials: {
|
|
ghTokenPresent: true,
|
|
githubTokenPresent: false,
|
|
ghHostsConfigPresent: false,
|
|
gitCredentialsPresent: false,
|
|
},
|
|
authBroker: {
|
|
ok: false,
|
|
configured: false,
|
|
source: "broker/auth-broker-needed",
|
|
endpointEnvKey: null,
|
|
runnerEnvTokenRequired: false,
|
|
credentialSource: null,
|
|
failureKind: "auth-missing",
|
|
degradedReason: "auth-broker-needed",
|
|
capability: {
|
|
source: "missing-token",
|
|
githubRestAuth: false,
|
|
operations: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"],
|
|
systemGhBinaryRequiredForWrites: false,
|
|
preflightWritesRemote: false,
|
|
preflightCreatesPr: false,
|
|
preflightMergesPr: false,
|
|
realPrCreateRequiresCommanderAuthorization: true,
|
|
valuesPrinted: false,
|
|
},
|
|
nextAction: "configure-auth-broker-or-env-token",
|
|
next: ["configure UNIDESK_AUTH_BROKER_URL or AUTH_BROKER_URL for broker-backed runner auth"],
|
|
valuesRead: false,
|
|
valuesPrinted: false,
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
}),
|
|
});
|
|
const envTokenRecord = asRecord(envTokenContract);
|
|
assertCondition(envTokenRecord.ok === true, "env token branch should be ready", envTokenRecord);
|
|
const envTokenPreflight = asRecord(envTokenRecord.preflight);
|
|
const envTokenCoverage = asRecord(envTokenPreflight.tokenCoverage);
|
|
const envTokenAuthBroker = asRecord(envTokenPreflight.authBroker);
|
|
assertCondition(envTokenCoverage.source === "GH_TOKEN", "env token branch should expose GH_TOKEN source", envTokenCoverage);
|
|
assertCondition(envTokenCoverage.credentialSource === "env-token", "env token branch should expose env-token capability", envTokenCoverage);
|
|
assertCondition(envTokenAuthBroker.source === "GH_TOKEN", "env token branch should not pretend broker is configured", envTokenAuthBroker);
|
|
assertCondition(envTokenAuthBroker.nextAction === "use-env-token-until-auth-broker-live", "env token branch should still point at broker migration", envTokenAuthBroker);
|
|
|
|
const missingTokenContract = await codexPrPreflightQueryForTest(["--remote"], {
|
|
config: null,
|
|
coreFetch: () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: { runtimePreflight: rawRuntimePreflightFixture() },
|
|
}),
|
|
});
|
|
const missingTokenRecord = asRecord(missingTokenContract);
|
|
assertCondition(missingTokenRecord.ok === false, "missing-token branch should fail", missingTokenRecord);
|
|
assertCondition(missingTokenRecord.failureKind === "auth-missing", "missing-token branch should classify auth-missing", missingTokenRecord);
|
|
assertCondition(missingTokenRecord.degradedReason === "auth-broker-needed", "missing-token branch should expose broker-needed degraded reason", missingTokenRecord);
|
|
const missingTokenPreflight = asRecord(missingTokenRecord.preflight);
|
|
const missingTokenAuthBroker = asRecord(missingTokenPreflight.authBroker);
|
|
const missingTokenScopeBoundary = asRecord(missingTokenPreflight.scopeBoundary);
|
|
const missingTokenActiveRunner = asRecord(missingTokenPreflight.activeRunnerDevContainer);
|
|
const missingTokenCapability = asRecord(missingTokenAuthBroker.capability);
|
|
assertCondition(missingTokenAuthBroker.source === "broker/auth-broker-needed", "missing-token branch should expose broker/auth-broker-needed", missingTokenAuthBroker);
|
|
assertCondition(missingTokenAuthBroker.nextAction === "configure-auth-broker-or-env-token", "missing-token branch should expose nextAction", missingTokenAuthBroker);
|
|
assertCondition(missingTokenCapability.source === "missing-token", "missing-token branch should expose missing-token capability", missingTokenCapability);
|
|
assertCondition(missingTokenCapability.systemGhBinaryRequiredForWrites === false, "missing-token branch should still not require system gh binary for UniDesk gh CLI", missingTokenCapability);
|
|
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh auth status"), "missing-token branch should point to active runner auth check", missingTokenScopeBoundary);
|
|
assertCondition(missingTokenActiveRunner.relationToRemotePreflight === "independent-scope; scheduler-runner-env auth-missing does not prove the active runner/dev container lacks GitHub PR capability", "missing-token branch should not overstate active runner PR capability", missingTokenActiveRunner);
|
|
|
|
process.stdout.write(`${JSON.stringify({
|
|
ok: true,
|
|
checks: [
|
|
"runner-like local target-stack absence does not block remote fallback",
|
|
"remote control plane fallback preserves ready preflight",
|
|
"missing remote control plane returns control-plane-missing",
|
|
"auth missing returns auth-missing with broker/auth-broker-needed",
|
|
"proxy failures return proxy-gap",
|
|
"git remote failures return git-remote-gap",
|
|
"combined push/PR create dry-run contract stays read-only and separates system gh from UniDesk gh CLI",
|
|
"broker-issued token, env-token, and missing-token branches expose authBroker source/capability/nextAction",
|
|
],
|
|
observedLocalPath,
|
|
observedDryRunPath,
|
|
}, null, 2)}\n`);
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
await main();
|
|
}
|