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
8 changes: 7 additions & 1 deletion .github/workflows/alpha-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,14 @@ jobs:
#
# 这一步同时覆盖了 alpha 自有的 opencode web search 闸 —— 那份行为闸住在
# packages/opencode/test,而本 job 的整包地板都不覆盖 opencode(全量 opencode 测试不属 alpha)。
#
# `#777`:这一步**不得**因为前一步红而被跳过。GitHub 的 step 默认条件是 `success()`,
# 于是 2026-08-02 起 `bun test (ui-mac)` 连红两天,把 77 个登记闸门一起带走 —— 其中
# llm / core / opencode 那几个**只在这一步执行**,别处再没有第二次机会。一步失败
# 连带 77 道门集体消失,是「主线红」这一件坏事自动升级成两件。
# 用 `!cancelled()` 而不是 `always()`:cancel-in-progress 取消掉的 run 不必再跑一遍。
- name: assert gate files (逐个点名,整包地板抓不到单文件消失)
if: needs.detect.outputs.code == 'true'
if: ${{ !cancelled() && needs.detect.outputs.code == 'true' }}
run: bash scripts/assert-gate-files.sh

seed-assets:
Expand Down
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@
```
bun install
bun run --cwd packages/ui-mac dev # electron 解析失败时加 ELECTRON_EXEC_PATH(见 ALPHA.md);flag 须在 run 后(REQ-027)
bash scripts/alpha-check.sh # push 前自检:北极星守卫 + typecheck + 单测(与 alpha-ci 1:1)
bash scripts/alpha-check.sh # push 前自检:alpha-ci 的全部 12 个代码步,末尾自陈逐步覆盖
```

## CI(规范见 `docs/runbooks/ci.md`)
**本地先跑,CI 兜底**。push 前必过 `scripts/alpha-check.sh`。GitHub 上只有 `alpha-ci`(三关,required)+ `sync-upstream` 两个 workflow active;继承的 ~26 个上游 workflow 已禁用(要 Blacksmith runner,本 fork 没有 → 永久 queued,即"CI 卡"真因)。排查手册见 `docs/runbooks/ci.md` §5。
**本地先跑,CI 兜底**。push 前必过 `scripts/alpha-check.sh` —— 它跑 `alpha-ci` 的**全部 12 个代码步**,
并在末尾打印逐步对照表(MIRRORED / SUPERSET / DEGRADED,后两者必须写理由)。这张表由
`packages/ui-mac/src/main/local-gate-parity.test.ts` 反向核对:CI 加一步而本地没登记即红。
`#777` 之前这里写的是「与 alpha-ci 1:1」,而实测只跑了 9 步、其中 3 步是裸 `bun test` 的降级档
(跑 0 条照样 exit 0)—— 一句没人核对的 1:1,比没有这句话更坏。
GitHub 上只有 `alpha-ci` + `sync-upstream` 两个 workflow active;继承的 ~26 个上游 workflow 已禁用(要 Blacksmith runner,本 fork 没有 → 永久 queued,即"CI 卡"真因)。排查手册见 `docs/runbooks/ci.md` §5。
**门自身的环境清单**(每道门 × 每个环境 × 真实状态)见 `docs/architecture/quality-gate-environments.md`。
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ accepted contracts. Follow the [Alpha Documentation Contract](https://github.com
| System structure and upstream boundary | [`architecture/`](architecture/) |
| Host extension package contract/host decision boundary | [`architecture/host-extension-package-contract-boundary.md`](architecture/host-extension-package-contract-boundary.md) |
| Alpha Connection record lifetime and handler allowlist | [`architecture/alpha-connection-lifetime.md`](architecture/alpha-connection-lifetime.md) |
| Which quality gate really runs in which environment (and where the environment is declared) | [`architecture/quality-gate-environments.md`](architecture/quality-gate-environments.md) |
| Platform and endpoint integration | [`contracts/`](contracts/) |
| Session tool permission DTOs and decision receipts | [`contracts/session-permission.md`](contracts/session-permission.md) |
| Build, distribution, CI, uninstall, and Settings recovery operations | [`runbooks/`](runbooks/) |
Expand Down
262 changes: 262 additions & 0 deletions docs/architecture/quality-gate-environments.md

Large diffs are not rendered by default.

49 changes: 38 additions & 11 deletions docs/runbooks/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@
bash scripts/alpha-check.sh
```

绿了再 push。它跑的就是 `alpha-ci` 的三关(北极星守卫 / typecheck / 单测),本地几秒出结果,和 CI 1:1 一致。
绿了再 push。它跑的是 `alpha-ci` 的**全部 12 个代码步**,并在末尾打印一张逐步对照表
(`MIRRORED` / `SUPERSET:<理由>` / `DEGRADED:<理由>`)。

> `#777` 起,「与 alpha-ci 1:1」不再是散文。2026-08-03 实读:此前这句话写在三处
> (本文件、`CLAUDE.md`、`scripts/alpha-check.sh` 抬头),而脚本实际只跑了 12 步里的 9 步 ——
> 缺 `assert-gate-files.sh`(77 个登记闸门里 llm / core / opencode 那几个**只在这一步执行**)、
> 缺 `assert-seed-assets.sh`、缺 `check-doc-links.py`;而且三条测试用的是裸 `bun test`
> (跑 0 条照样 exit 0 —— CI 早在 `#647` 修掉的假绿形态,本地原样留着)。
> **「本地绿 ⇒ 可以合」这条铁律的全部依据就是这句 1:1**,所以它现在由
> `packages/ui-mac/src/main/local-gate-parity.test.ts` 双向核对:CI 加一步而对照表没登记即红,
> CI 改一个步骤名而对照表没跟也即红。

## 1. 为什么 local-first(优先本地跑)

Expand All @@ -18,15 +28,21 @@ bash scripts/alpha-check.sh
- **CI 的真正职责**是①**强制门禁**(branch protection:不绿不让 merge,挡手滑)②**中立环境兜底**(抓"在我机器上是好的"那类环境差异)——不是给你当调试台用的。
- 所以顺序永远是:**本地 `alpha-check` 绿 → push → CI 复核 → merge**。

## 2. 三关是什么(与 `alpha-ci` 1:1)
## 2. 七关是什么(逐步对照见 `alpha-check.sh` 的 `CI_STEPS`)

| 关 | 本地命令 | CI job | 失败含义 |
| 关 | 本地步骤 | CI job | 失败含义 |
|---|---|---|---|
| **北极星守卫**(零改上游) | `scripts/alpha-check.sh` 内含;等价 `git diff --diff-filter=DMR --name-only origin/dev...HEAD -- <上游7包>` 必须空 | `north-star guard (zero upstream edits)` | 改了上游文件 → 下次 fork-sync 冲突,破北极星 |
| **typecheck**(三个 alpha 包) | `bun run --cwd packages/alpha-contracts-consumer typecheck` + `bun run --cwd packages/ext typecheck` + `bun run --cwd packages/ui-mac typecheck` | `typecheck (alpha packages)` | 类型不过 |
| **契约锁 + 单元测试** | `packages/alpha-contracts-consumer` 的 `check:vendor`/`bun test`,再从 `packages/ext`、`packages/ui-mac` 各跑 `bun test` | `unit tests (alpha packages)` | vendored hash、producer/consumer fixture 或运行时守卫回归 |

- **上游 7 包** = `packages/{opencode,core,server,app,ui,tui,sdk}`(见 alpha-ci.yml `env.UPSTREAM_PATHS`)。
| **北极星守卫**(零改上游) | `[1/7]`;等价 `git diff --diff-filter=DMR --name-only origin/dev...HEAD -- <上游 8 包>` 必须空 | `north-star guard (zero upstream edits)` | 改了上游文件 → 下次 fork-sync 冲突,破北极星 |
| **NUL 字节闸** | `[2/7]` `scripts/assert-no-nul-bytes.py` | 同上 job | 字面 NUL 让 `grep` 对整个文件静默失明(`#760`) |
| **typecheck**(三个 alpha 包) | `[3/7]` contracts-consumer + ext + ui-mac | `typecheck (alpha packages)` | 类型不过 |
| **契约锁 + 单元测试** | `[4/7]` `check:vendor` + `bun-test-floor.sh` × 3(15 / 100 / 3000) | `unit tests (alpha packages)` | vendored hash、producer/consumer fixture 或运行时守卫回归 |
| **闸门文件点名** | `[5/7]` `scripts/assert-gate-files.sh`(77 个) | 同上 job | 某个闸门文件被删/被清空 —— 整包地板抓不到 |
| **seed assets** | `[6/7]` `scripts/assert-seed-assets.sh` | `seed assets present` | 打包资源被静默删除(B7/B15) |
| **docs gate** | `[7/7]` `scripts/check-doc-links.py <改动的 md>` | `docs gate` | Markdown 相对链接断了 |

- **上游包** = `packages/{opencode,core,server,tui,sdk,protocol,schema,client}`(**8 个**,见 alpha-ci.yml
`env.UPSTREAM_PATHS`;`app`/`ui` 已按 ADR-020 移出守卫,`protocol`/`schema`/`client` 按 ADR-033 补入)。
这份清单与 24 条 ADR-033 收编白名单,两处必须逐条相同 —— 由 `local-gate-parity.test.ts` 判(`#637`)。
- **bun 版本钉 `1.3.14`**(与 CI 一致,根 `package.json` 的 `packageManager`)。本机版本不同先对齐。
- 根 `bun test` 被故意禁用(`do not run tests from root`)——测试按包跑,别在根跑。
- Alpha Platform wire pin 不使用 `bun.lock`。`check:vendor` 要求
Expand Down Expand Up @@ -64,7 +80,15 @@ bash scripts/alpha-check.sh
| **`alpha-ci`** | 本仓 CI(上面三关) | `push` / `pull_request` → `alpha`;也可 `workflow_dispatch` |
| **`sync-upstream`** | 每日上游 `dev → merge alpha` 同步 | 定时 + 手动 |

- **required check 只有 `alpha-ci` 的三个 job**;其它 workflow 一律**不影响 merge**(PR 上若看到别的 check = 历史残留,忽略)。
- **分支保护 required contexts 与真实 job 名对不上,`unit tests (ui-mac)` 是个幽灵**(2026-08-03 实读,`#717`)。
`alpha` 保护要求四个 context:`north-star guard (zero upstream edits)` / `typecheck (alpha packages)` /
**`unit tests (ui-mac)`** / `docs gate`。而那个 job 早在 2026-07-22(`ebd29cda`)就改名成
`unit tests (alpha packages)` —— **没有任何 PR 会产出 `unit tests (ui-mac)` 这个 context**,
code PR 也不会。后果:每个 PR 在这一格上永久 pending ⇒ 每次合并都得 `--admin` ⇒
主线真红的时候也没有任何东西挡住。**「大家习惯性 --admin」不是纪律松懈,是分支保护自己造出来的。**
`#717` 的票面把成因写成「docs-only path 没发布该 context」,与实测不符:改名是全量的。
修法(改的是 GitHub 设置,不在本仓文件里)由 `#717` 承担。
- 其它 workflow 一律**不影响 merge**(PR 上若看到别的 check = 历史残留,忽略)。
- **不要盲目 `gh workflow enable`** 上游那些——除非你真给 fork 接了对应 runner。要恢复某个:`gh workflow enable <name>.yml`(可逆,文件没删)。

## 4. 提交纪律
Expand All @@ -81,11 +105,14 @@ bash scripts/alpha-check.sh
- 一直 `queued` = 等 runner(无匹配 runner / Actions 额度耗尽 / 并发上限);
- `in_progress` 慢 = job 本身慢(装依赖 / 测试)——那才是 REQ-009 缓存优化的场景。
3. **命令**:`gh run cancel <id>` 取消 · `gh run watch <id>` 盯 · `gh run rerun <id>` 重跑 · `gh run list --workflow=alpha-ci.yml` 只看本仓 CI。
4. **别被堵**:本地三关已绿 = 代码没问题;required 只有 alpha-ci 三个。真急可 `gh pr merge --admin` 绕过(慎用,别养成习惯)。
4. **别被堵**:本地七步已绿 = 代码没问题。注意 §3 那条 —— 目前 required 里有一个**永远不会上报**的
context(`unit tests (ui-mac)`,`#717`),所以「PR 卡着不动」多半不是 CI 慢,是那一格永远 pending。
真急可 `gh pr merge --admin` 绕过(它现在是**结构性必需**,不是纪律松懈;`#717` 修好之前别把
「又 --admin 了一次」当成有人偷懒)。

## 6. pre-push 钩子 —— local-first 强制(2026-07-05 REQ-015 起默认开启)

`.githooks/pre-push` = 跑 `scripts/alpha-check.sh`( alpha-ci 1:1)。**默认开启**:`alpha-check.sh` 每次运行都会幂等重挂 `git config core.hooksPath .githooks`。
`.githooks/pre-push` = 跑 `scripts/alpha-check.sh`(覆盖 alpha-ci 全部 12 个代码步,末尾自陈对照表)。**默认开启**:`alpha-check.sh` 每次运行都会幂等重挂 `git config core.hooksPath .githooks`。

为什么不能用上游 husky 门(此前「配置过又失效」的根因,REQ-015):
- `.husky/pre-push` 跑**全量** `bun turbo typecheck`,在 ADR-020 冻结偏斜下 `session-ui` 恒红(上游叶子包,alpha 不 ship,权威门 alpha-ci 不含)→ 逼出 `--no-verify` 习惯;
Expand Down
69 changes: 64 additions & 5 deletions packages/ui-mac/scripts/test-preload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,65 @@
// bun test preload: pin the UI locale for the whole test process before any test file
// evaluates. Consumed by src/renderer/i18n/index.ts detectLocale(), which prefers an
// explicit ALPHA_UI_LOCALE over navigator sniffing. `||=` respects an override the caller
// already exported (e.g. to run the suite in another locale). Child processes spawned by
// tests (alpha-composer-model.component.test.ts) inherit this via process.env.
// bun test preload —— **本包全部测试进程的环境声明处**(唯一一处)。
//
// 这个文件是 `#777` 的咽喉:一道门跑在什么环境里,由这里一次性声明,而不是由每个测试文件
// 各自记得。`packages/ui-mac/bunfig.toml` 的 `preload` 把它注入到每一个 `bun test` 进程 ——
// 包括 host 测试用 `Bun.spawnSync([process.execPath, "test", …], { cwd: packages/ui-mac })`
// 起的**子进程**(子进程 cwd 就是本包,于是读到同一份 bunfig)。新增一道门什么都不用记,
// 环境是默认给到的;想覆盖仍然可以在单条用例上显式写(显式值恒胜,已实测)。
//
// 每一条都必须是**声明 + 自陈**:静默地改环境等于把恒红换成假绿。

import { setDefaultTimeout } from "bun:test"

// ── ① UI 语言 ────────────────────────────────────────────────────────────────
// Consumed by src/renderer/i18n/index.ts detectLocale(), which prefers an explicit
// ALPHA_UI_LOCALE over navigator sniffing. `||=` respects an override the caller already
// exported (e.g. to run the suite in another locale). Child processes spawned by tests
// (alpha-composer-model.component.test.ts) inherit this via process.env.
process.env.ALPHA_UI_LOCALE ||= "zh"

// ── ② 每条用例的默认超时(**只管单文件运行**,权威在 scripts/bun-test-floor.sh)────────
// bun 默认 5000ms,对「在子进程里跑一整套 `.cases.ts`」的 host 用例来说,那不是超时,
// 是**机器速度在替断言下判决**(2026-08-02 主线连红两天,三条即此)。
//
// ⚠️ 这一行的作用范围**远小于它看起来的样子** —— 实测(bun 1.3.14):
// `setDefaultTimeout()` 从 preload 调用,**只对一次运行的第一个测试文件生效**;
// `bun test src`(257 个文件)从第二个文件起就退回 5000ms。
// 最初的探针只跑了单文件,于是给出一个**假的通过** —— 观测手段自己有盲区。
// `beforeAll(() => setDefaultTimeout(...))` 同样无效。跨全部文件唯一有效的是
// `bun test --timeout N`,而那个 flag 现在由 `scripts/bun-test-floor.sh` 一处给出。
//
// 那为什么这里还留着:**host 起的子进程都是单文件运行**(`bun test <一个绝对路径>`),
// 它们的 argv 是 host 自己拼的、传不进 flag —— 那一半正好落在本行的有效范围内。
// 两处合起来才覆盖两种形状,两处都有行为闸看着(`src/main/gate-environment.test.ts`)。
//
// 取值 120s:与仓内已显式声明超时的那批同档(最慢的正当 host 在 CI 上实测 37.7s)。
// 需要把时长本身当断言的用例(如 artifact-quota 的 `}, 1000)`)照旧显式写,显式值恒胜。
setDefaultTimeout(120_000)

// ── ③ 平台:**只检测 + 自陈,不改** ────────────────────────────────────────────
// 本产品 ship 的桌面平台是 darwin + win32(ADR-026;`ext-install-planner.ts` 的
// `synthesizeManifest` 把 `compatibility.platforms` 写死成 `["darwin","win32"]`)。
// alpha-ci 的 runner 是 ubuntu ⇒ 凡是走到生产平台闸的用例,在写盘前就被拒:
// `platform linux not supported by this entry — refusing before any disk write`
// 它们量到的是「runner 不是我们发布的平台」,不是它们存在的理由。
//
// ⚠️ 本票**试过**在这里全局钉 `process.platform = "darwin"`,在 alpha-ci 上实测**炸得更狠**:
// 14 个 renderer 测试文件整片挂在
// `error: Cannot find module @rollup/rollup-darwin-x64`
// —— vite/rollup 按 `process.platform` 选原生可选依赖,而 linux runner 上装的是 linux 那份。
// 3752 pass 掉到 3625。**为了修 2 条而弄坏 14 个文件,那是把一处假红换成一片真红。**
// 教训写在这里而不是只写在票里:全局改环境的代价,必须在真环境里量过才算知道。
//
// 所以平台是**按需 opt-in**:需要走生产平台闸的 cases 文件自己调
// `test-component/pin-shipped-platform.ts` 的 `pinShippedPlatform()`(它同样会自陈)。
// 这里只负责让「本次运行的 host 不是发布平台」这件事**说出口** —— 一道门可以降级,
// 但不许静默降级;而看到这行的人才知道要去 opt-in。
export const SHIPPED_PLATFORMS = ["darwin", "win32"] as const
if (!(SHIPPED_PLATFORMS as readonly string[]).includes(process.platform)) {
console.log(
`[alpha-test-env] host platform "${process.platform}" is not one this product ships on ` +
`(${SHIPPED_PLATFORMS.join("/")}). PLATFORM DEGRADED this run: any gate that reaches a production ` +
`platform check refuses before doing anything, and measures the runner instead of the behaviour. ` +
`Such gates must opt in via pinShippedPlatform() (test-component/pin-shipped-platform.ts).`,
)
}
Loading
Loading