diff --git a/docs/reference/auth-broker.md b/docs/reference/auth-broker.md
index 726f5b9a..8491cc51 100644
--- a/docs/reference/auth-broker.md
+++ b/docs/reference/auth-broker.md
@@ -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 不需要为了调研临时安装新系统包。
diff --git a/docs/reference/code-queue-supervision.md b/docs/reference/code-queue-supervision.md
index 4a5770d0..08008a80 100644
--- a/docs/reference/code-queue-supervision.md
+++ b/docs/reference/code-queue-supervision.md
@@ -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
`;该 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`。
diff --git a/scripts/code-queue-pr-preflight-contract-test.ts b/scripts/code-queue-pr-preflight-contract-test.ts
index 515dc08a..45f0a8f3 100644
--- a/scripts/code-queue-pr-preflight-contract-test.ts
+++ b/scripts/code-queue-pr-preflight-contract-test.ts
@@ -342,6 +342,11 @@ async function main(): Promise {
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 {
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 {
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,
diff --git a/scripts/src/code-queue.ts b/scripts/src/code-queue.ts
index 6e04e402..b7f43a48 100644
--- a/scripts/src/code-queue.ts
+++ b/scripts/src/code-queue.ts
@@ -2897,25 +2897,74 @@ function activeRunnerDevContainerCapability(): Record {
function prPreflightScopeBoundary(tokenCoverage: Record | null): Record {
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 | null): Record[] {
+ 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 --body-file --base master --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 | null, activeRunnerDevContainer: Record): Record {
+ 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): Record {
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 | null): Record | 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, remoteRecord: Record): Record {
+ 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, options: CodexPrPreflightOptions): Record {
const pull = asRecord(preflight.pullRequestDelivery) ?? {};
const tools = asRecord(pull.tools) ?? {};
@@ -2978,6 +3074,10 @@ function compactPrRuntimePreflight(preflight: Record, options:
const pushDryRunRef = options.pushDryRunRef ?? defaultPushDryRunRef;
const targetBranch = "master";
const expectedHeadBranch = options.prCreateDryRunHead ?? (typeof git.branch === "string" && git.branch.length > 0 ? git.branch : "");
+ const scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
+ const activeRunnerDevContainer = activeRunnerDevContainerCapability();
+ const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
+ const recommendedActions = prPreflightRecommendedActions(tokenCoverage);
const result: Record = {
ok,
failureKind,
@@ -2992,7 +3092,9 @@ function compactPrRuntimePreflight(preflight: Record, 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, 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",