From 3bded7e0f86044c6e7ca5e17b2a3a159570d1542 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 14 Jul 2026 12:13:00 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8A=95=E5=BD=B1=20Artificer=20?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E4=BB=93=20GitHub=20=E5=87=AD=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/agentrun.yaml | 24 +++++++ config/unidesk-cli.yaml | 7 +- .../src/gh-token-repository-override.test.ts | 66 +++++++++++++++++++ scripts/src/gh/auth-and-safety.ts | 13 +++- scripts/src/gh/default-render.ts | 2 + scripts/src/gh/types.ts | 4 +- 6 files changed, 106 insertions(+), 10 deletions(-) diff --git a/config/agentrun.yaml b/config/agentrun.yaml index d52c08c9..134310da 100644 --- a/config/agentrun.yaml +++ b/config/agentrun.yaml @@ -313,6 +313,7 @@ controlPlane: localPostgresExpectedAbsent: true toolCredentials: - extends: controlPlane.templates.toolCredentials.githubPr + - extends: controlPlane.templates.toolCredentials.githubRepositoryOverridePikaoa - extends: controlPlane.templates.toolCredentials.unideskSsh - extends: controlPlane.templates.toolCredentials.githubSsh secrets: @@ -462,6 +463,7 @@ controlPlane: providerCredential: profile: dsflash-go - extends: controlPlane.templates.secrets.githubPrRawToken + - extends: controlPlane.templates.secrets.githubRepositoryOverridePikaoaToken - extends: controlPlane.templates.secrets.unideskSshToken - extends: controlPlane.templates.secrets.githubSshPrivateKey - extends: controlPlane.templates.secrets.githubSshKnownHosts @@ -534,6 +536,19 @@ controlPlane: kind: env envName: GH_TOKEN secretKey: GH_TOKEN + githubRepositoryOverridePikaoa: + id: github-repository-override-pikaoa + tool: github + purpose: repository-override-pikainc-pikaoa + secretRef: + namespace: agentrun-v02 + name: agentrun-v02-tool-github-repository-overrides + keys: + - PIKAINC_PIKAOA_GH_TOKEN + projection: + kind: env + envName: UNIDESK_GH_TOKEN_PIKAINC_PIKAOA + secretKey: PIKAINC_PIKAOA_GH_TOKEN unideskSsh: id: unidesk-ssh tool: unidesk-ssh @@ -570,6 +585,15 @@ controlPlane: namespace: agentrun-v02 name: agentrun-v01-tool-github-pr key: GH_TOKEN + githubRepositoryOverridePikaoaToken: + id: tool-github-repository-override-pikaoa-token + sourceRef: /root/.unidesk/.env/pikainc-selfmedia-gh-token.txt + sourceKey: GH_TOKEN + sourceFormat: raw-token + targetRef: + namespace: agentrun-v02 + name: agentrun-v02-tool-github-repository-overrides + key: PIKAINC_PIKAOA_GH_TOKEN unideskSshToken: id: tool-unidesk-ssh-token sourceRef: /root/.unidesk/.state/docker-compose.env diff --git a/config/unidesk-cli.yaml b/config/unidesk-cli.yaml index 1a5ec1ea..de5d1975 100644 --- a/config/unidesk-cli.yaml +++ b/config/unidesk-cli.yaml @@ -21,18 +21,13 @@ github: sourceRef: pikainc-selfmedia-gh-token.txt sourceKey: GH_TOKEN format: raw-token + projectedEnv: UNIDESK_GH_TOKEN_PIKAINC_PIKAOA - repository: pikainc/selfmedia priority: before-env root: /root/.unidesk/.env sourceRef: pikainc-selfmedia-gh-token.txt sourceKey: GH_TOKEN format: raw-token - - repository: pikainc/pikaoa - priority: before-env - root: /root/.unidesk/.env - sourceRef: pikainc-selfmedia-gh-token.txt - sourceKey: GH_TOKEN - format: raw-token prMerge: unknownRetry: maxAttempts: 5 diff --git a/scripts/src/gh-token-repository-override.test.ts b/scripts/src/gh-token-repository-override.test.ts index e1e3fff9..659ffc40 100644 --- a/scripts/src/gh-token-repository-override.test.ts +++ b/scripts/src/gh-token-repository-override.test.ts @@ -2,11 +2,14 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { resolveToken, type GitHubYamlAuthConfig } from "./gh/auth-and-safety"; +import { authRequired } from "./gh/client"; import { resolvePositionalIssueReference, resolvePositionalPrReference } from "./gh/refs"; import type { GitHubOptions, GitHubResolvedNumberReference } from "./gh/types"; const originalGhToken = process.env.GH_TOKEN; const originalGithubToken = process.env.GITHUB_TOKEN; +const projectedEnv = "UNIDESK_TEST_GH_REPOSITORY_OVERRIDE"; +const originalProjectedToken = process.env[projectedEnv]; const temporaryRoots: string[] = []; afterEach(() => { @@ -14,6 +17,8 @@ afterEach(() => { else process.env.GH_TOKEN = originalGhToken; if (originalGithubToken === undefined) delete process.env.GITHUB_TOKEN; else process.env.GITHUB_TOKEN = originalGithubToken; + if (originalProjectedToken === undefined) delete process.env[projectedEnv]; + else process.env[projectedEnv] = originalProjectedToken; while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop()!, { recursive: true, force: true }); }); @@ -84,6 +89,67 @@ describe("GitHub repository token overrides", () => { expect(selected.probe).toMatchObject({ source: "yaml-token-source", scope: "repository-override", yamlSourcePriority: "before-env" }); }); + test("uses the YAML-declared projected environment for repository overrides", () => { + process.env.GH_TOKEN = "global-environment-token"; + process.env[projectedEnv] = "projected-repository-token"; + const authConfig = testAuthConfig(); + authConfig.repositoryOverrides[0] = { + ...authConfig.repositoryOverrides[0], + sourceRef: "missing-token.txt", + projectedEnv, + }; + + const selected = resolveToken("pikainc/selfmedia", false, authConfig); + + expect(selected.token).toBe("projected-repository-token"); + expect(selected.probe).toMatchObject({ + source: "yaml-token-projection", + scope: "repository-override", + sourceExists: false, + sourceKeyPresent: false, + projectedEnv, + projectedEnvPresent: true, + valuesPrinted: false, + }); + }); + + test("fails closed when a declared repository override is missing", () => { + process.env.GH_TOKEN = "global-environment-token"; + delete process.env[projectedEnv]; + const authConfig = testAuthConfig(); + authConfig.repositoryOverrides[0] = { + ...authConfig.repositoryOverrides[0], + sourceRef: "missing-token.txt", + projectedEnv, + }; + + const selected = resolveToken("pikainc/selfmedia", true, authConfig); + const failure = authRequired("pikainc/selfmedia", "pr view", selected.probe); + + expect(selected.token).toBeNull(); + expect(selected.probe).toMatchObject({ + present: false, + scope: "repository-override", + sourceExists: false, + sourceKeyPresent: false, + projectedEnv, + projectedEnvPresent: false, + ghFallbackAttempted: false, + valuesPrinted: false, + }); + expect(failure).toMatchObject({ + ok: false, + degradedReason: "missing-token", + runnerDisposition: "infra-blocked", + token: { + scope: "repository-override", + projectedEnvPresent: false, + valuesPrinted: false, + }, + }); + expect(JSON.stringify(failure)).not.toContain("global-environment-token"); + }); + test("matches repository overrides without case sensitivity", () => { delete process.env.GH_TOKEN; delete process.env.GITHUB_TOKEN; diff --git a/scripts/src/gh/auth-and-safety.ts b/scripts/src/gh/auth-and-safety.ts index b9542157..73e1adee 100644 --- a/scripts/src/gh/auth-and-safety.ts +++ b/scripts/src/gh/auth-and-safety.ts @@ -21,6 +21,7 @@ export interface GitHubYamlTokenSourceConfig { sourceRef: string; sourceKey: string; format: "env" | "raw-token"; + projectedEnv?: string; scope: "repository-override" | "global"; configRef: string; repository?: string; @@ -88,13 +89,16 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf const sourcePath = resolveYamlSourcePath(sourceRoot, config.sourceRef); const sourceExists = existsSync(sourcePath); const values = sourceExists ? parseYamlTokenSource(readFileSync(sourcePath, "utf8"), config) : {}; - const token = values[config.sourceKey] ?? null; + const sourceToken = values[config.sourceKey] ?? null; + const projectedToken = config.projectedEnv === undefined ? null : process.env[config.projectedEnv] ?? null; + const token = sourceToken ?? projectedToken; const present = token !== null && token.length > 0; + const projectedEnvPresent = projectedToken !== null && projectedToken.length > 0; return { token: present ? token : null, probe: { present, - source: present ? "yaml-token-source" : null, + source: sourceToken !== null ? "yaml-token-source" : projectedEnvPresent ? "yaml-token-projection" : null, scope: config.scope, ghFallbackAttempted: false, yamlSourceAttempted: true, @@ -104,7 +108,8 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf sourceRef: config.sourceRef, sourceKey: config.sourceKey, sourceExists, - sourceKeyPresent: present, + sourceKeyPresent: sourceToken !== null && sourceToken.length > 0, + ...(config.projectedEnv === undefined ? {} : { projectedEnv: config.projectedEnv, projectedEnvPresent }), tokenFingerprint: present ? fingerprintToken(token) : null, valuesPrinted: false, }, @@ -132,6 +137,7 @@ export function ghAuthToken(): string | null { export function resolveToken(repo: string, allowGhFallback: boolean, authConfig?: GitHubYamlAuthConfig): { token: string | null; probe: GitHubTokenProbe } { const yamlToken = tokenFromYamlSource(repo, authConfig); if (yamlToken.probe.yamlSourcePriority === "before-env" && yamlToken.probe.present) return yamlToken; + if (yamlToken.probe.scope === "repository-override" && !yamlToken.probe.present) return yamlToken; const envProbe = tokenFromEnvironment(); if (envProbe.present) { const token = envProbe.source === "GH_TOKEN" ? process.env.GH_TOKEN ?? null : process.env.GITHUB_TOKEN ?? null; @@ -179,6 +185,7 @@ function readGitHubYamlAuthConfig(): GitHubYamlAuthConfig { sourceRef: stringField(override, "sourceRef", configRef), sourceKey: envKeyField(override, "sourceKey", configRef), format: tokenSourceFormatField(override, "format", configRef), + ...(override.projectedEnv === undefined ? {} : { projectedEnv: envKeyField(override, "projectedEnv", configRef) }), scope: "repository-override" as const, configRef, }; diff --git a/scripts/src/gh/default-render.ts b/scripts/src/gh/default-render.ts index 3fa3a819..4560513b 100644 --- a/scripts/src/gh/default-render.ts +++ b/scripts/src/gh/default-render.ts @@ -388,6 +388,8 @@ function renderAuthStatus(result: GitHubCommandResult): string { `priority=${ghText(token.yamlSourcePriority)}`, `sourceRef=${ghText(token.sourceRef)}`, `key=${ghText(token.sourceKey)}`, + `projectedEnv=${ghText(token.projectedEnv)}`, + `projectedEnvPresent=${ghText(token.projectedEnvPresent)}`, `fingerprint=${ghText(token.tokenFingerprint)}`, `ghFallback=${ghText(token.ghFallbackAttempted)}`, ].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" "); diff --git a/scripts/src/gh/types.ts b/scripts/src/gh/types.ts index 4bd2086c..de62ad84 100644 --- a/scripts/src/gh/types.ts +++ b/scripts/src/gh/types.ts @@ -410,7 +410,7 @@ export type GitHubCommandFailure = GitHubCommandResult & { ok: false }; export interface GitHubTokenProbe { present: boolean; - source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "gh-auth-token" | null; + source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "yaml-token-projection" | "gh-auth-token" | null; scope?: "repository-override" | "global"; ghFallbackAttempted: boolean; ghBinaryFound?: boolean; @@ -423,6 +423,8 @@ export interface GitHubTokenProbe { sourceKey?: string; sourceExists?: boolean; sourceKeyPresent?: boolean; + projectedEnv?: string; + projectedEnvPresent?: boolean; tokenFingerprint?: string | null; valuesPrinted?: false; }