Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/bug-server-pr-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/bug-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions docs/superpowers/plans/2026-09-17-bug-server-pr-routing.md
Original file line number Diff line number Diff line change
@@ -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 检查为准。
12 changes: 10 additions & 2 deletions docs/superpowers/specs/2026-09-17-bug-server-dispatch-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@

## 数据流与权限边界

1. `bug-server-pr-bundle.yml` 仅由 `pull_request` 触发。在只读仓库权限、无持久化 checkout 凭据、无 Bug Server token 的 runner 上检出准确 head SHA,执行 Rush 构建。缓存写入作用域属于该 PR,不属于默认分支。产物名为 `bug-server-pr-<number>-<sha>`,保留 7 天。
1. `bug-server-pr-bundle.yml` 仅在外部 fork 的 `pull_request` 中执行构建。在只读仓库权限、无持久化 checkout 凭据、无 Bug Server token 的 runner 上检出准确 head SHA,执行 Rush 构建。缓存写入作用域属于该 PR,不属于默认分支。产物名为 `bug-server-pr-<number>-<sha>`,保留 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 测试及仓库提交、推送检查。

## 维护者操作变化

Expand Down
22 changes: 18 additions & 4 deletions tools/bugserver-trigger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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-<number>-<sha>`.
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-<number>-<sha>`.
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.

Expand Down
Loading