From 3eb0dc4cd0bef9e77c7fe5aa9cdadf3bf546d038 Mon Sep 17 00:00:00 2001 From: xile611 Date: Thu, 17 Sep 2026 17:11:05 +0800 Subject: [PATCH] ci: route Bug Server builds by PR source --- .github/workflows/bug-server-pr-bundle.yml | 1 + .github/workflows/bug-server.yml | 7 ++-- .../plans/2026-09-17-bug-server-pr-routing.md | 36 +++++++++++++++++++ .../2026-09-17-bug-server-dispatch-design.md | 12 +++++-- tools/bugserver-trigger/README.md | 22 +++++++++--- 5 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 docs/superpowers/plans/2026-09-17-bug-server-pr-routing.md diff --git a/.github/workflows/bug-server-pr-bundle.yml b/.github/workflows/bug-server-pr-bundle.yml index fa86e3e01..9dbe19e59 100644 --- a/.github/workflows/bug-server-pr-bundle.yml +++ b/.github/workflows/bug-server-pr-bundle.yml @@ -9,6 +9,7 @@ permissions: jobs: build-pr-bundle: + if: github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest timeout-minutes: 30 steps: diff --git a/.github/workflows/bug-server.yml b/.github/workflows/bug-server.yml index 384fa2d81..bfdc2a810 100644 --- a/.github/workflows/bug-server.yml +++ b/.github/workflows/bug-server.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: inputs: pr_number: - description: 'PR number to test (including fork PRs)' + description: 'Fork PR number to test' required: true type: string head_sha: @@ -22,9 +22,10 @@ permissions: jobs: build: - if: github.event_name != 'workflow_dispatch' + if: >- + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - name: Use Node.js 24.x diff --git a/docs/superpowers/plans/2026-09-17-bug-server-pr-routing.md b/docs/superpowers/plans/2026-09-17-bug-server-pr-routing.md new file mode 100644 index 000000000..b0c45da5e --- /dev/null +++ b/docs/superpowers/plans/2026-09-17-bug-server-pr-routing.md @@ -0,0 +1,36 @@ +# Bug Server PR 来源分流实现计划 + +**目标:** 消除同一 PR 在 Bug Server CI 与 Bug Server PR Bundle 中的重复构建。 + +**架构:** 按 PR head 仓库完整名称与当前仓库名称比较,在两个构建 job 上设置互斥条件。仓库内 PR 自动构建和测试,fork PR 构建产物后由维护者通过现有手动入口提交测试。 + +**技术栈:** GitHub Actions、actionlint、Node.js、Python。 + +## 约束 + +- 基于最新 `develop` 创建 `codex/bugserver-pr-routing`,向 `develop` 提交 PR。 +- PR 目标分支维持 `main`、`develop`、`dev/**`,保留 `main` push 和手动入口。 +- 维持现有构建命令、Node.js 24、Ubuntu runner、权限和产物校验边界。 +- 分流采用 job 级条件;另一构建 job 显示 `skipped`,不启动 runner。 + +## 任务 1:修改 workflow 与说明 + +文件:`.github/workflows/bug-server.yml`、`.github/workflows/bug-server-pr-bundle.yml`、`tools/bugserver-trigger/README.md`、`docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md`。 + +- [x] 将 CI 的 `build.if` 改为 `github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)`。 +- [x] 为 `build-pr-bundle` 增加 `if: github.event.pull_request.head.repo.full_name != github.repository`。 +- [x] 说明手动入口依赖 fork PR 的 bundle,仓库内 PR 应重跑 CI;同步输入描述和执行边界。 + +## 任务 2:验证与提交 + +- [x] 读取实际 YAML 条件,校验仓库内 PR、外部 fork PR、同组织不同仓库 PR、`main` push、手动触发共 5 种场景。 +- [x] 运行 `node --test .github/scripts/bug-server-dispatch.test.cjs`、`python3 -B -m unittest discover -s .github/scripts -p 'test_extract_bug_server_bundle.py'`、两个 workflow 的 actionlint、Prettier 与 `git diff --check`。 + +提交阶段通过仓库推送钩子,创建面向 `develop` 的 PR,并核对 PR 的分支及线上检查启动状态;发布结果记录在 PR 说明中。 + +## 本地验证结果 + +- 5 种事件场景通过,PR 目标分支过滤和手动提交依赖保持正确。 +- Node 来源校验测试 36 项、Python ZIP 校验测试 6 项全部通过。 +- actionlint 1.7.12、Prettier 和 `git diff --check` 通过。 +- 未修改构建命令和测试客户端,本次不重复完整构建。线上回归结果以 PR 检查为准。 diff --git a/docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md b/docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md index 603c95698..498ccbcb0 100644 --- a/docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md +++ b/docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md @@ -6,13 +6,21 @@ ## 数据流与权限边界 -1. `bug-server-pr-bundle.yml` 仅由 `pull_request` 触发。在只读仓库权限、无持久化 checkout 凭据、无 Bug Server token 的 runner 上检出准确 head SHA,执行 Rush 构建。缓存写入作用域属于该 PR,不属于默认分支。产物名为 `bug-server-pr--`,保留 7 天。 +1. `bug-server-pr-bundle.yml` 仅在外部 fork 的 `pull_request` 中执行构建。在只读仓库权限、无持久化 checkout 凭据、无 Bug Server token 的 runner 上检出准确 head SHA,执行 Rush 构建。缓存写入作用域属于该 PR,不属于默认分支。产物名为 `bug-server-pr--`,保留 7 天。 2. `bug-server.yml` 的手动入口只允许默认分支,保留 `pr_number`、`head_sha`。可信脚本校验输入、base 仓库及 PR 当前 head,再从指定 PR bundle workflow 查找成功运行。 3. 来源校验绑定 workflow ID/路径、事件、运行状态、base/head 仓库 ID、源分支及 run head SHA。fork 的运行记录可能没有 PR 列表,不能因此拒绝所有外部 PR;如列表存在则还需匹配 PR 编号。 4. 选择最新匹配运行中唯一且未过期的命名产物,复核 artifact API 的 run ID、仓库 ID 与 SHA。失败时要求先成功运行 PR bundle 工作流,不回退到其他提交或较旧运行。 5. 提交 job 只检出 `github.workflow_sha` 对应的可信脚本。通过 artifact ID 下载 ZIP,只接受一个名为 `index.js` 的普通文件,最大 64 MiB。可信 Python 脚本只把文件字节写入固定位置,不按 ZIP 路径解压,不执行产物。 6. 可信 TypeScript 客户端的依赖独立安装且禁用 lifecycle scripts。仅最后的 API 调用 step 注入 `BUG_SERVER_TOKEN`;PR 元数据与产物来源由可信校验 job 提供。summary 记录 PR、SHA 和来源构建。 -7. 两个 workflow 默认 `contents: read`;查询 PR 需要 `pull-requests: read`,查询/下载 artifact 需要 `actions: read`。原有 push / pull_request 自动 Bug Server 步骤保持原来的构建和测试行为。 +7. 两个 workflow 默认 `contents: read`;查询 PR 需要 `pull-requests: read`,查询/下载 artifact 需要 `actions: read`。`main` push 和仓库内 PR 保留自动构建和测试。 + +## 按 PR 来源分流 + +对于目标分支为 `main`、`develop`、`dev/**` 的 PR,比较 `github.event.pull_request.head.repo.full_name` 与 `github.repository`:相等时仅执行 **Bug Server CI** 的 `build` job,不相等时仅执行 **Bug Server PR Bundle** 的 `build-pr-bundle` job。每个 PR 事件只实际构建一次;同组织的其他仓库也属于外部来源。 + +`pull_request` 事件过滤器不能按来源仓库过滤,因此使用 job 级 `if`。两个 workflow 仍可能出现运行记录,但另一构建 job 会显示 `skipped`,不启动 runner。`main` push 继续自动构建和测试;手动入口仍然只校验并提交 fork PR 的已有产物。仓库内 PR 需要重测时重跑 CI,不再生成手动入口需要的 bundle。 + +验证仓库内 PR、外部 fork PR、同组织不同仓库 PR、`main` push 和手动触发的执行矩阵,并运行 actionlint、既有 Node/Python 测试及仓库提交、推送检查。 ## 维护者操作变化 diff --git a/tools/bugserver-trigger/README.md b/tools/bugserver-trigger/README.md index 6eafaa8f0..7c79fd80b 100644 --- a/tools/bugserver-trigger/README.md +++ b/tools/bugserver-trigger/README.md @@ -2,11 +2,25 @@ `scripts/trigger-test.ts` uploads `dist/index.js`, waits for an SCM build, starts the Bug Server photo tests, and waits for their results. It requires `BUG_SERVER_TOKEN`. -## Manually test a PR +## Automatic runs + +PRs targeting `main`, `develop` or `dev/**` are routed by their source repository, so each PR event builds only once: + +| Event | Bug Server CI | Bug Server PR Bundle | +| ------------------ | ---------------------------------- | ------------------------------------------------- | +| Same-repository PR | Build and submit tests | Skip the build | +| Fork PR | Skip the automatic build and tests | Build and save the artifact for manual submission | +| Push to `main` | Build and submit tests | Not triggered | + +The job conditions compare `github.event.pull_request.head.repo.full_name` with `github.repository`. GitHub cannot filter `pull_request` triggers by source repository, so both workflows may appear in Actions, but the other build job is skipped without starting a runner. + +To retest a same-repository PR, re-run **Bug Server CI**. The manual entry below uses fork PR artifacts; same-repository PRs no longer produce a **Bug Server PR Bundle** artifact. + +## Manually test a fork PR After the workflows are merged into the repository's default branch (`develop`), wait for **Bug Server PR Bundle** to succeed for the reviewed PR head. Fork runs may need a maintainer's approval. Then maintainers with repository write access can open **Actions → Bug Server CI → Run workflow**. Select **develop**, then enter: -- `pr_number`: the PR number, including PRs from external forks. +- `pr_number`: the number of a PR submitted to this repository from an external fork. - `head_sha`: the full 40-character SHA of the PR head that you reviewed. The equivalent CLI command is: @@ -27,11 +41,11 @@ The manual entry consumes an existing PR bundle; it does not build PR code. Arti ## Execution boundaries -1. **Bug Server PR Bundle** runs only on `pull_request`, builds the exact head with read-only repository permissions, disabled persisted checkout credentials and no Bug Server token. Any cache writes are confined to the PR scope. It uploads `bug-server-pr--`. +1. **Bug Server PR Bundle** builds only for fork `pull_request` events, using the exact head with read-only repository permissions, disabled persisted checkout credentials and no Bug Server token. Any cache writes are confined to the PR scope. It uploads `bug-server-pr--`. 2. The manual **resolve-manual-target** job validates the current PR head and source workflow ID/path, PR event, successful run, repository IDs, source branch and run SHA. It requires one non-expired artifact with matching GitHub API provenance. Fork runs can omit PR associations; the repository/branch/SHA checks still bind the source. 3. **submit-manual-bundle** uses scripts from the immutable default-branch workflow commit. It downloads the selected artifact ID and accepts only a single regular `index.js` entry, up to 64 MiB. The trusted extractor writes bytes to a fixed path without extracting archive paths. The client only uploads those bytes; it never executes the bundle or PR package scripts. -Both workflows default to `contents: read`. Manual lookup and download jobs also need `actions: read`, and target validation needs `pull-requests: read`. The Bug Server token is injected only into the final API client step. Existing push and pull-request automatic runs retain their build and test behavior with read-only repository permissions. A fork PR's automatic Bug Server run still cannot obtain repository secrets; use the manual entry for Bug Server validation. +Both workflows default to `contents: read`. Manual lookup and download jobs also need `actions: read`, and target validation needs `pull-requests: read`. The Bug Server token is injected only into the final API client step. Pushes to `main` and same-repository PRs retain their automatic build and test behavior with read-only repository permissions. Fork PRs automatically build only the artifact; use the manual entry for Bug Server validation. The default-branch manual workflow does not check out or build PR code. This replaces the earlier `cache-mode` approach and does not require scanner exceptions.