fix: clarify pr preflight auth scopes
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
- `scripts/src/gh.ts`:UniDesk `gh` CLI 已经是 repo-native GitHub REST wrapper,优先读取 `GH_TOKEN`,其次读取 `GITHUB_TOKEN`,再 fallback 到系统 `gh auth token`。它不会打印 token 值,并已把 `missing-token`、`permission-denied`、`scope-insufficient`、`network-proxy-failed`、`unsupported-command` 等失败结构化。
|
||||
- `scripts/code-queue-pr-preflight-example.ts`:本地示例先检查 env token,再跑 `gh auth status`、`gh pr create --dry-run` 和 `gh pr comment --dry-run`。没有 env token 时该 workflow 直接失败。
|
||||
- `src/components/microservices/code-queue/src/runtime-preflight.ts`:D601 scheduler preflight 检查 `GH_TOKEN` / `GITHUB_TOKEN` 是否存在,用 curl/gh/git 探测 GitHub API、issue、PR、SSH 和可选 push dry-run。
|
||||
- `scripts/src/code-queue.ts`:`codex pr-preflight` 把 runtime preflight 压缩为 runner 合同;`tokenCoverageStatus` 目前要求 env token,缺失时输出 `failureKind=auth-missing`、`degradedReason=GH_TOKEN/GITHUB_TOKEN missing` 和 `runnerDisposition=infra-blocked`。
|
||||
- `scripts/src/code-queue.ts`:`codex pr-preflight` 把 runtime preflight 压缩为 runner 合同;scheduler env token 与 active runner/dev container token 是独立 scope。缺少 scheduler env token 或 auth-broker 时输出 `failureKind=auth-missing`、`degradedReason=auth-broker-needed`、`runnerDisposition=infra-blocked`,并用 `authScopeSummary`/`scopeBoundary`/`recommendedActions` 明确这不等于当前 active runner 不能创建 PR;active task 内仍需用 repo-native `bun scripts/cli.ts gh auth status` 和 PR create dry-run 验证。
|
||||
- `src/components/microservices/code-queue/src/index.ts`:`CODE_QUEUE_REMOTE_CODEX_ENV_KEYS` 默认包含 `GH_TOKEN`、`GITHUB_TOKEN`、`GH_HOST`、`GITHUB_API_URL` 和 `GH_REPO`,用于把 scheduler env 继续传给 provider dev container。这是凭证分发路径,不适合作为 P0 的唯一解。
|
||||
- `src/components/microservices/code-queue/docker-compose.d601.yml`:D601 Code Queue 从 `.state/code-queue-d601.env` 读取运行时环境,并配置 provider-gateway egress proxy。该文件不能承载本任务中的真实 secret 值。
|
||||
- `src/components/microservices/code-queue/Dockerfile`:runner 镜像已经包含 `git`、`gh`、`curl`、`jq`、`rg`、`cargo`、`rustc` 和 `rustfmt`,足够执行轻量 broker client、preflight 和 contract test;本 P0 不需要为了调研临时安装新系统包。
|
||||
|
||||
@@ -161,7 +161,7 @@ Runner preflight 优先使用执行面诊断入口:
|
||||
bun scripts/cli.ts codex pr-preflight --remote --issue 20
|
||||
```
|
||||
|
||||
`codex pr-preflight --remote` 的 `auth-missing` 只表示 scheduler/runtime preflight surface(`scheduler-runner-env`)没有看到 `GH_TOKEN/GITHUB_TOKEN` 或 auth-broker,不得被简化成“当前 active runner/dev container 不能创建 PR”。Code Queue 输出必须同时给出 `scopeBoundary` 和 `activeRunnerDevContainer`:前者说明 scheduler env 与当前 CLI/dev container 是独立 scope,后者只报告当前 CLI 进程是否看见 token,且不打印 token 值。指挥官看到 remote preflight `auth-missing` 时,应继续用当前 runner 内的 `bun scripts/cli.ts gh auth status --repo pikasTech/unidesk`、`gh pr create --dry-run`、`gh pr comment create --dry-run` 验证实际 PR 能力;只有这些 active runner 检查也失败时,才能把它判成当前 turn 不能 PR。
|
||||
`codex pr-preflight --remote` 的 `auth-missing` 只表示 scheduler/runtime preflight surface(`scheduler-runner-env`)没有看到 `GH_TOKEN/GITHUB_TOKEN` 或 auth-broker,不得被简化成“当前 active runner/dev container 不能创建 PR”。默认输出必须优先给出有界的 `authScopeSummary`、`scopeBoundary`、`activeRunnerDevContainer` 和 `recommendedActions`:`authScopeSummary` 用一句话说明 scheduler auth missing 只是 scoped finding;`scopeBoundary` 明确 scheduler env 与当前 CLI/dev container 是独立 scope;`activeRunnerDevContainer` 只报告当前 CLI 进程是否看见 token,且不打印 token 值;`recommendedActions` 保持低噪声,先给 active task 内的 `bun scripts/cli.ts gh auth status --repo pikasTech/unidesk` 和 `gh pr create --dry-run`,再给 scheduler 侧长期的 auth-broker 或 runtime secret 修复。指挥官看到 remote preflight `auth-missing` 时,应继续用当前 runner 内的 `bun scripts/cli.ts gh auth status --repo pikasTech/unidesk`、`gh pr create --dry-run`、`gh pr comment create --dry-run` 验证实际 PR 能力;只有这些 active runner 检查也失败时,才能把它判成当前 turn 不能 PR。
|
||||
|
||||
该命令经 backend-core 稳定 `code-queue` proxy 访问 D601 scheduler 的 `/api/runtime-preflight`,报告 scheduler/runner 环境里的 `GH_TOKEN`/`GITHUB_TOKEN` 覆盖、工具、Git worktree、GitHub egress、repo/issue/PR 只读探测和可选 push dry-run。需要复核 PR body/创建命令 guard 时追加 `--pr-create-dry-run --pr-create-dry-run-head <head>`;该 guard 只执行 dry-run,不创建 PR。缺少 env token 时必须返回 `ok=false`、`runnerDisposition=infra-blocked`、`tokenCoverage.missing=["GH_TOKEN","GITHUB_TOKEN"]` 和 `authBroker.source="broker/auth-broker-needed"`,因为 provider dev container 只能转发 scheduler 已经拥有的 token,除非后续接入 broker-held GitHub credential。系统 `gh` binary 缺失只能作为 `tools.systemGhBinary.ok=false` 观测,不得把它误判为 UniDesk REST `bun scripts/cli.ts gh` 不可用。`--remote` 在 runner-like 环境里不再要求本地 `unidesk-backend-core`、`unidesk-database`、`baidu-netdisk-backend` 容器存在;这些本地 target stack 缺失只作为证据,不是最终主阻塞,并应额外标成 `blockingDisposition=runner-local-observation-gap` 或 `localObservationGap.kind=runner-local-observation-gap`。若远程控制面可达,输出继续保留 ready preflight;若远程控制面不可达,结构化失败归类为 `failureKind=control-plane-missing` / `degradedReason=remote-control-plane-unreachable`,并额外标成 `blockingDisposition=control-plane-observation-gap`。`runnerDisposition` 可以为了旧调用方兼容继续保持 `infra-blocked`,但 observation-gap 字段才是判断“观测路径缺口,不是 scheduler 停摆”的稳定口径。输出中的 `prCapabilityContract` 用于指挥官快速审查 runner handoff:目标分支固定显示、push/PR create dry-run 标记为不写远端、系统 `gh` binary 与 UniDesk REST `bun scripts/cli.ts gh` 可用性分开报告,且 merge 明确保持 `unsupported-command`。
|
||||
|
||||
|
||||
@@ -342,6 +342,11 @@ async function main(): Promise<void> {
|
||||
const fallbackLocalGap = asRecord(fallback.localObservationGap);
|
||||
assertCondition(fallbackLocalGap.kind === "runner-local-observation-gap", "healthy remote fallback should classify local backend-core absence as runner-local observation gap", fallbackLocalGap);
|
||||
assertCondition(fallbackLocalGap.schedulerStoppage === false, "local observation gap must not imply scheduler stoppage", fallbackLocalGap);
|
||||
assertCondition(fallback.localObservation === undefined, "healthy remote fallback default output should omit full local observation", fallback);
|
||||
assertCondition(fallback.remoteObservation === undefined, "healthy remote fallback default output should omit full remote observation", fallback);
|
||||
assertCondition(asRecord(fallback.disclosure).fullObservationsOmitted === true, "healthy remote fallback should disclose omitted full observations", fallback.disclosure);
|
||||
assertCondition(asRecord(fallback.localObservationSummary).failureKind === "target-stack-not-running", "healthy remote fallback should keep bounded local observation summary", fallback.localObservationSummary);
|
||||
assertCondition(asRecord(fallback.remoteObservationSummary).ok === true, "healthy remote fallback should keep bounded remote observation summary", fallback.remoteObservationSummary);
|
||||
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);
|
||||
@@ -399,11 +404,45 @@ async function main(): Promise<void> {
|
||||
assertCondition(directAuthMissingRecord.degradedReason === "GH_TOKEN/GITHUB_TOKEN missing", "auth missing should state token gap", directAuthMissingRecord);
|
||||
const directAuthObservationGap = asRecord(directAuthMissingRecord.observationGap);
|
||||
assertCondition(directAuthObservationGap.kind === "runner-local-observation-gap", "auth missing after remote fallback should keep local backend-core absence scoped as runner-local observation gap", directAuthObservationGap);
|
||||
const directAuthSummary = asRecord(directAuthMissingRecord.authScopeSummary);
|
||||
const directAuthScopeBoundary = asRecord(directAuthMissingRecord.scopeBoundary);
|
||||
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerDevContainer);
|
||||
const directAuthRecommendedActions = Array.isArray(directAuthMissingRecord.recommendedActions) ? directAuthMissingRecord.recommendedActions : [];
|
||||
assertCondition(directAuthSummary.schedulerAuthMissingIsScoped === true, "remote auth-missing should lead with scheduler-scoped auth summary", directAuthSummary);
|
||||
assertCondition(String(directAuthSummary.interpretation ?? "").includes("does not prove"), "remote auth summary must not imply active runner PR incapability", directAuthSummary);
|
||||
assertCondition(directAuthScopeBoundary.scopesAreIndependent === true, "remote auth-missing must distinguish scheduler env from active runner dev container", directAuthScopeBoundary);
|
||||
assertCondition(directAuthScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "remote auth-missing should expose the explicit PR capability boundary", 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);
|
||||
assertCondition(Array.isArray(directAuthMissingRecord.recommendedActions), "remote auth-missing should expose bounded recommended actions", directAuthMissingRecord.recommendedActions);
|
||||
assertCondition(directAuthRecommendedActions.length === 3, "remote auth-missing recommended actions should stay bounded", directAuthRecommendedActions);
|
||||
assertCondition(directAuthRecommendedActions.some((action) => asRecord(action).action === "verify-current-runner-auth"), "remote auth-missing should recommend active runner auth status first", directAuthRecommendedActions);
|
||||
assertCondition(directAuthRecommendedActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "remote auth-missing should recommend PR create dry-run", directAuthRecommendedActions);
|
||||
assertCondition(directAuthMissingRecord.localObservation === undefined, "auth-missing remote fallback default output should omit full local observation", directAuthMissingRecord);
|
||||
assertCondition(directAuthMissingRecord.remoteObservation === undefined, "auth-missing remote fallback default output should omit full remote observation", directAuthMissingRecord);
|
||||
assertCondition(asRecord(directAuthMissingRecord.disclosure).fullObservationsOmitted === true, "auth-missing remote fallback should disclose omitted full observations", directAuthMissingRecord.disclosure);
|
||||
assertCondition(asRecord(directAuthMissingRecord.localObservationSummary).failureKind === "target-stack-not-running", "auth-missing remote fallback should keep bounded local observation summary", directAuthMissingRecord.localObservationSummary);
|
||||
assertCondition(asRecord(asRecord(directAuthMissingRecord.remoteObservationSummary).tokenCoverage).scope === "scheduler-runner-env", "auth-missing remote fallback should keep bounded remote token scope", directAuthMissingRecord.remoteObservationSummary);
|
||||
|
||||
const directAuthMissingFull = await codexPrPreflightQueryForTest(["--remote", "--full"], {
|
||||
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",
|
||||
tokenCoverage: {
|
||||
ok: false,
|
||||
source: null,
|
||||
missing: ["GH_TOKEN", "GITHUB_TOKEN"],
|
||||
scope: "scheduler-runner-env",
|
||||
},
|
||||
}),
|
||||
});
|
||||
const directAuthMissingFullRecord = asRecord(directAuthMissingFull);
|
||||
assertCondition(directAuthMissingFullRecord.localObservation !== undefined, "--full should retain full local observation", directAuthMissingFullRecord);
|
||||
assertCondition(directAuthMissingFullRecord.remoteObservation !== undefined, "--full should retain full remote observation", directAuthMissingFullRecord);
|
||||
|
||||
const gitRemoteGap = remoteControlPlaneResult({
|
||||
ok: false,
|
||||
@@ -747,17 +786,30 @@ async function main(): Promise<void> {
|
||||
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 missingTokenTopSummary = asRecord(missingTokenRecord.authScopeSummary);
|
||||
const missingTokenTopScopeBoundary = asRecord(missingTokenRecord.scopeBoundary);
|
||||
const missingTokenTopActions = Array.isArray(missingTokenRecord.recommendedActions) ? missingTokenRecord.recommendedActions : [];
|
||||
const missingTokenPreflight = asRecord(missingTokenRecord.preflight);
|
||||
const missingTokenAuthBroker = asRecord(missingTokenPreflight.authBroker);
|
||||
const missingTokenSummary = asRecord(missingTokenPreflight.authScopeSummary);
|
||||
const missingTokenScopeBoundary = asRecord(missingTokenPreflight.scopeBoundary);
|
||||
const missingTokenActiveRunner = asRecord(missingTokenPreflight.activeRunnerDevContainer);
|
||||
const missingTokenActions = Array.isArray(missingTokenPreflight.recommendedActions) ? missingTokenPreflight.recommendedActions : [];
|
||||
const missingTokenCapability = asRecord(missingTokenAuthBroker.capability);
|
||||
assertCondition(missingTokenTopSummary.schedulerAuthMissingIsScoped === true, "missing-token top-level summary should expose scoped scheduler auth missing", missingTokenTopSummary);
|
||||
assertCondition(missingTokenTopScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "missing-token top-level boundary should be prominent", missingTokenTopScopeBoundary);
|
||||
assertCondition(Array.isArray(missingTokenRecord.recommendedActions) && missingTokenTopActions.length === 3, "missing-token top-level recommended actions should stay bounded", missingTokenRecord.recommendedActions);
|
||||
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(missingTokenSummary.schedulerAuthMissingIsScoped === true, "missing-token compact preflight should expose scoped scheduler auth missing", missingTokenSummary);
|
||||
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh auth status"), "missing-token branch should point to active runner auth check", missingTokenScopeBoundary);
|
||||
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh pr create --dry-run"), "missing-token branch should point to active runner PR create dry-run", 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);
|
||||
assertCondition(Array.isArray(missingTokenPreflight.recommendedActions) && missingTokenActions.length === 3, "missing-token compact recommended actions should stay bounded", missingTokenPreflight.recommendedActions);
|
||||
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh auth status")), "missing-token branch should recommend gh auth status", missingTokenActions);
|
||||
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "missing-token branch should recommend PR create dry-run without writes", missingTokenActions);
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
|
||||
+118
-10
@@ -2897,25 +2897,74 @@ function activeRunnerDevContainerCapability(): Record<string, unknown> {
|
||||
function prPreflightScopeBoundary(tokenCoverage: Record<string, unknown> | null): Record<string, unknown> {
|
||||
const schedulerScope = typeof tokenCoverage?.scope === "string" ? tokenCoverage.scope : "scheduler-runner-env";
|
||||
return {
|
||||
headline: "scheduler-runner-env auth-missing is not active runner/dev container PR incapability",
|
||||
schedulerPreflightScope: schedulerScope,
|
||||
activeRunnerDevContainerScope: "current-cli-process",
|
||||
scopesAreIndependent: true,
|
||||
schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr: true,
|
||||
authMissingInterpretation: "auth-missing from codex pr-preflight --remote is scoped to the scheduler/runtime preflight surface; do not simplify it to 'the active runner cannot create PRs'",
|
||||
currentRunnerCheck: "use activeRunnerDevContainer plus bun scripts/cli.ts gh auth status --repo pikasTech/unidesk for the current dev container",
|
||||
currentRunnerCheck: "use activeRunnerDevContainer plus bun scripts/cli.ts gh auth status --repo pikasTech/unidesk and gh pr create --dry-run for the current dev container",
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function prPreflightRecommendedActions(tokenCoverage: Record<string, unknown> | null): Record<string, unknown>[] {
|
||||
const schedulerReady = tokenCoverage?.ok === true;
|
||||
return [
|
||||
{
|
||||
scope: "active-runner-dev-container",
|
||||
priority: "first",
|
||||
action: "verify-current-runner-auth",
|
||||
command: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||||
writesRemote: false,
|
||||
},
|
||||
{
|
||||
scope: "active-runner-dev-container",
|
||||
priority: "first",
|
||||
action: "verify-current-runner-pr-create-plan",
|
||||
command: "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title <title> --body-file <file> --base master --head <head> --dry-run",
|
||||
writesRemote: false,
|
||||
},
|
||||
{
|
||||
scope: "scheduler-runtime",
|
||||
priority: schedulerReady ? "long-term" : "required-for-scheduler-preflight",
|
||||
action: "configure-scheduler-auth-source",
|
||||
command: "configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN via the scheduler runtime secret",
|
||||
writesRemote: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function prPreflightAuthScopeSummary(tokenCoverage: Record<string, unknown> | null, activeRunnerDevContainer: Record<string, unknown>): Record<string, unknown> {
|
||||
const schedulerReady = tokenCoverage?.ok === true;
|
||||
return {
|
||||
schedulerPreflightScope: typeof tokenCoverage?.scope === "string" ? tokenCoverage.scope : "scheduler-runner-env",
|
||||
schedulerAuthReady: schedulerReady,
|
||||
schedulerAuthSource: tokenCoverage?.source ?? null,
|
||||
schedulerAuthMissingIsScoped: !schedulerReady,
|
||||
activeRunnerDevContainerScope: activeRunnerDevContainer.scope ?? "current-cli-process",
|
||||
activeRunnerTokenCandidatePresent: activeRunnerDevContainer.ok === true,
|
||||
interpretation: schedulerReady
|
||||
? "scheduler preflight has GitHub auth coverage; active runner PR creation still needs repo-native dry-run verification before real writes"
|
||||
: "scheduler-runner-env auth-missing does not prove the active runner/dev container lacks PR create/comment capability",
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function decoratePrPreflightScopeBoundary(record: Record<string, unknown>): Record<string, unknown> {
|
||||
const preflight = asRecord(record.preflight);
|
||||
const tokenCoverage = asRecord(preflight?.tokenCoverage ?? record.tokenCoverage);
|
||||
const tokenCoverage = asRecord(record.tokenCoverage) ?? asRecord(preflight?.tokenCoverage);
|
||||
const scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
|
||||
const activeRunnerDevContainer = activeRunnerDevContainerCapability();
|
||||
const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
|
||||
const recommendedActions = prPreflightRecommendedActions(tokenCoverage);
|
||||
return {
|
||||
...record,
|
||||
authScopeSummary,
|
||||
scopeBoundary,
|
||||
activeRunnerDevContainer,
|
||||
...(preflight === null ? {} : { preflight: { ...preflight, scopeBoundary } }),
|
||||
recommendedActions,
|
||||
...(preflight === null ? {} : { preflight: { ...preflight, authScopeSummary, scopeBoundary, recommendedActions } }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2943,6 +2992,53 @@ function prPreflightObservationGap(kind: Exclude<CodeQueueObservationGapKind, nu
|
||||
};
|
||||
}
|
||||
|
||||
function prPreflightObservationSummary(record: Record<string, unknown> | null): Record<string, unknown> | null {
|
||||
if (record === null) return null;
|
||||
const preflight = asRecord(record.preflight);
|
||||
const tokenCoverage = asRecord(record.tokenCoverage) ?? asRecord(preflight?.tokenCoverage);
|
||||
const controlPlane = asRecord(record.controlPlane);
|
||||
const targetStack = asRecord(record.targetStack);
|
||||
return {
|
||||
ok: record.ok ?? null,
|
||||
runnerDisposition: record.runnerDisposition ?? null,
|
||||
failureKind: record.failureKind ?? null,
|
||||
degradedReason: record.degradedReason ?? null,
|
||||
controlPlane: controlPlane === null ? null : {
|
||||
mode: controlPlane.mode ?? null,
|
||||
localBackendCoreMissing: controlPlane.localBackendCoreMissing ?? null,
|
||||
remoteFallbackUsed: controlPlane.remoteFallbackUsed ?? null,
|
||||
},
|
||||
targetStack: targetStack === null ? null : {
|
||||
missingContainers: Array.isArray(targetStack.missingContainers) ? targetStack.missingContainers.map(String) : [],
|
||||
verifyOnlyObserved: targetStack.verifyOnlyObserved ?? false,
|
||||
},
|
||||
tokenCoverage: tokenCoverage === null ? null : {
|
||||
ok: tokenCoverage.ok ?? null,
|
||||
source: tokenCoverage.source ?? null,
|
||||
credentialSource: tokenCoverage.credentialSource ?? null,
|
||||
scope: tokenCoverage.scope ?? null,
|
||||
missing: Array.isArray(tokenCoverage.missing) ? tokenCoverage.missing.map(String) : [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function maybeFullPrPreflightObservations(options: CodexPrPreflightOptions, localRecord: Record<string, unknown>, remoteRecord: Record<string, unknown>): Record<string, unknown> {
|
||||
if (options.full) {
|
||||
return {
|
||||
localObservation: localRecord,
|
||||
remoteObservation: remoteRecord,
|
||||
};
|
||||
}
|
||||
return {
|
||||
localObservationSummary: prPreflightObservationSummary(localRecord),
|
||||
remoteObservationSummary: prPreflightObservationSummary(remoteRecord),
|
||||
disclosure: {
|
||||
fullObservationsOmitted: true,
|
||||
expandWith: "bun scripts/cli.ts codex pr-preflight --remote --full",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function compactPrRuntimePreflight(preflight: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
|
||||
const pull = asRecord(preflight.pullRequestDelivery) ?? {};
|
||||
const tools = asRecord(pull.tools) ?? {};
|
||||
@@ -2978,6 +3074,10 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
|
||||
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 scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
|
||||
const activeRunnerDevContainer = activeRunnerDevContainerCapability();
|
||||
const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
|
||||
const recommendedActions = prPreflightRecommendedActions(tokenCoverage);
|
||||
const result: Record<string, unknown> = {
|
||||
ok,
|
||||
failureKind,
|
||||
@@ -2992,7 +3092,9 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
|
||||
},
|
||||
tokenCoverage,
|
||||
authBroker: authBrokerNeededStatus(tokenCoverage, authBrokerRuntime, systemGhBinary, unideskGhCli),
|
||||
scopeBoundary: prPreflightScopeBoundary(tokenCoverage),
|
||||
authScopeSummary,
|
||||
scopeBoundary,
|
||||
recommendedActions,
|
||||
prCapabilityContract: {
|
||||
targetBranch,
|
||||
tokenSource: tokenCoverage.source,
|
||||
@@ -3089,12 +3191,12 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
|
||||
limitations,
|
||||
risks,
|
||||
runnerDisposition: ok ? "ready" : "infra-blocked",
|
||||
activeRunnerDevContainer: activeRunnerDevContainerCapability(),
|
||||
activeRunnerDevContainer,
|
||||
recoveryHint: ok
|
||||
? tokenCoverage.source === "auth-broker"
|
||||
? "Runner PR workflow has auth-broker coverage for GitHub REST preflight; real PR creation still requires commander authorization."
|
||||
: "Runner PR workflow has env-token coverage for the scheduler."
|
||||
: "Scheduler preflight lacks GitHub auth coverage. Configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret for scheduler-scoped admission; separately check activeRunnerDevContainer and gh auth status before declaring the current runner unable to create/comment PRs.",
|
||||
: "Scheduler preflight lacks GitHub auth coverage for scheduler-scoped admission only. First verify the active task with repo-native gh auth status and PR create dry-run; long-term configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret.",
|
||||
commands: {
|
||||
local: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||||
runner: "bun scripts/cli.ts codex pr-preflight --remote",
|
||||
@@ -3230,8 +3332,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
|
||||
localBackendCoreMissing: true,
|
||||
remoteFallbackUsed: true,
|
||||
},
|
||||
localObservation: localRecord,
|
||||
remoteObservation: remoteRecord,
|
||||
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
|
||||
});
|
||||
}
|
||||
return decoratePrPreflightScopeBoundary({
|
||||
@@ -3250,8 +3351,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
|
||||
localBackendCoreMissing: true,
|
||||
remoteFallbackUsed: true,
|
||||
},
|
||||
localObservation: localRecord,
|
||||
remoteObservation: remoteRecord,
|
||||
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3347,6 +3447,10 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
|
||||
runnerDisposition: compact.runnerDisposition,
|
||||
failureKind: compact.failureKind ?? null,
|
||||
degradedReason: compact.degradedReason ?? null,
|
||||
authScopeSummary: compact.authScopeSummary,
|
||||
scopeBoundary: compact.scopeBoundary,
|
||||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||||
recommendedActions: compact.recommendedActions,
|
||||
upstream: { ok: response.ok, status: response.status },
|
||||
controlPlane: {
|
||||
mode: "local-backend-core",
|
||||
@@ -3399,6 +3503,10 @@ export async function codexPrPreflightQueryAsync(optionArgs: string[], fetcher:
|
||||
runnerDisposition: compact.runnerDisposition,
|
||||
failureKind: compact.failureKind ?? null,
|
||||
degradedReason: compact.degradedReason ?? null,
|
||||
authScopeSummary: compact.authScopeSummary,
|
||||
scopeBoundary: compact.scopeBoundary,
|
||||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||||
recommendedActions: compact.recommendedActions,
|
||||
upstream: { ok: response.ok, status: response.status },
|
||||
controlPlane: {
|
||||
mode: "remote-frontend",
|
||||
|
||||
Reference in New Issue
Block a user