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
64 changes: 64 additions & 0 deletions .github/workflows/sync-upstream-alert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# alpha-code auto-sync — ALERT phase (`#995`).
#
# 每日 `sync-upstream` 失败时**通知一个人**。此前它失败的唯一输出是一条 Actions 日志,
# 而没有任何东西读那条日志:实测(2026-09-19 勘破)最后一次成功是 `2026-07-22T08:06:35Z`,
# 此后 **59 次连续失败、零通知**;9 月初上游是人手工追平的(`ac#1248`),cron 仍在每天红。
#
# 这个文件与 `sync-upstream-push.yml` 是**同一个形状的两半**:候选 job
# (`sync-upstream.yml`,无任何凭据、执行上游代码)跑完之后,由两个 `workflow_run` 消费者
# 分别接走两种结局 —— `success` 归推送,`failure` 归本文件。此前只有前一半有消费者,
# 失败那一半的 `conclusion` **没有任何读者**,这就是 `#995` 的全部内容。
#
# `#899`(SEC)的同一条纪律:本 job 持有 `issues: write`,所以它**不执行合并树里的任何代码** ——
# checkout 的是 `alpha`(我们自己的树)、**不跑 `bun install`**、只跑一个零依赖的
# `scripts/sync-upstream-alert.ts`。不要往这个文件里加安装/构建/跑引擎的步骤:那会把一个
# 有写权限的 token 放进与上游代码同一个进程,正是 `#899` 拆两半要消灭的形状。
#
# 行为闸:`packages/ui-mac/src/main/sync-upstream-alert.test.ts` —— 它起一个真的 HTTP 服务
# 冒充 GitHub API,跑**生产的那个脚本本体**,断言它真的发出了写入;并从本文件解析接线
# (监视哪条 workflow、只在 failure 时跑、权限、没有 install 步)。
name: sync-upstream-alert

on:
workflow_run:
workflows: ["sync-upstream"]
types: [completed]

# `issues: write` 是这个 job 存在的全部理由。**不要**在这里加 `contents: write`
# 或引用 `secrets.SYNC_TOKEN` —— 推送凭据只属于 sync-upstream-push.yml。
# `actions: read` 用于读本次 run 的结论、失败步与连败长度。
permissions:
contents: read
issues: write
actions: read

# 刻意**不**与 sync-upstream 共用 concurrency group:共用会让告警排在下一轮候选后面,
# 而告警的价值与时效直接相关。
concurrency:
group: sync-upstream-alert
cancel-in-progress: false

jobs:
alert:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- name: Checkout alpha (our own tree, no push credential)
uses: actions/checkout@v4
with:
ref: alpha
persist-credentials: false

- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"

# 零依赖:这一步**没有** `bun install`。脚本只用 `fetch`,见其抬头的信任边界一节。
- name: File or refresh the sync-upstream failure issue
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
ALERT_RUN_ID: ${{ github.event.workflow_run.id }}
ALERT_WORKFLOW: sync-upstream.yml
run: bun scripts/sync-upstream-alert.ts
58 changes: 58 additions & 0 deletions docs/architecture/upstream-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,64 @@ order and assert the same fixture dies with `failed to resolve` and prints no
`::error::` at all — the gate is proven to detect the known-bad before it is
trusted about the unknown-good.

### A failing sync had no reader for two months (`#995`)

Measured 2026-09-19 from the real run history, not inferred from the workflow:
the last successful `sync-upstream` run was `2026-07-22T08:06:35Z`
(run `29902706228`), and every run since — **59 consecutive daily runs,
2026-07-23 through 2026-09-19** — failed. Nobody was notified for any of them.
Upstream reached Alpha in that window exactly once, by hand (`ac#1248`,
`efced9fa9`, 2026-09-06).

The streak is **not one cause**. Classifying each failing run by the step the
Actions API reports as failed (the `##[error]Unexpected merge conflicts` string
also appears in the *echoed script body*, so a plain `grep` for it matches every
run — that fingerprint is useless):

| window | runs | what actually failed |
| --- | --- | --- |
| 2026-07-23 → 2026-08-07 | 16 | `bun install` — `@opencode-ai/client@file:../app/vendor/opencode-ai-client-1.17.13[-v2].tgz failed to resolve`. This is the pre-`#1272` order defect above. The 07-23 run had already merged and pushed (`e77266975..02407fcfa alpha -> alpha`) before dying in the smoke step. |
| 2026-08-08 → 2026-09-06 | 30 | merge aborted — unexpected conflict in `packages/desktop/src/main/index.ts` (plus `packages/ui/src/v2/components/dialog-v2.tsx` from 09-06). |
| 2026-09-07 → 2026-09-19 | 13 | merge aborted — unexpected conflict in `packages/opencode/test/provider/transform.test.ts`. |

The current abort is **correct behaviour, and it will recur**. The conflicting
file is on the north-star annexation whitelist
([`scripts/north-star-guard.sh`](../../scripts/north-star-guard.sh)
`UPSTREAM_EXCLUDES`, 48 entries): Alpha deliberately edits it (`#1147`), so the
merge step's message "the only-add discipline was broken" is literally false
here — the discipline was waived on purpose. Every annexed upstream file is a
permanent conflict generator, and a conflict there genuinely requires a human
(auto-resolving would silently drop one side). `ac#1248` demonstrates the shape:
its catch-up merge landed 2026-09-06 and the very next run, 09-07, aborted again
because upstream had moved on — 7 of the 272 upstream commits since that
merge-base touch `transform.test.ts`.

So the defect `#995` fixes is not the abort. It is that
`sync-upstream.yml`'s failure conclusion had **no reader at all**:
[`sync-upstream-push.yml`](../../.github/workflows/sync-upstream-push.yml)
consumes `conclusion == 'success'` and nothing consumed the other half.
[`sync-upstream-alert.yml`](../../.github/workflows/sync-upstream-alert.yml) is
that missing half, built to the same trust shape: a `workflow_run` consumer that
holds a write scope (`issues: write`) precisely because it executes **no code
from the merged tree** — it checks out `alpha`, runs no `bun install`, and
invokes only the dependency-free
[`scripts/sync-upstream-alert.ts`](../../scripts/sync-upstream-alert.ts).

That script files or refreshes one tracking Issue labelled
`sync-upstream-failure`: it creates and assigns on the first failure of a
streak, re-comments only when the failing step changes, and otherwise just
refreshes the body with the streak length and the latest run. Fifty-nine daily
comments would be silenced as fast as fifty-nine silent failures were ignored.
Every API error, and a missing token, exit non-zero — a notification that did
not go out must turn its own run red rather than return 0.

The gate is
[`packages/ui-mac/src/main/sync-upstream-alert.test.ts`](../../packages/ui-mac/src/main/sync-upstream-alert.test.ts):
it serves a real HTTP stub of the GitHub API, spawns the production script
against it, and asserts the writes it actually issued. Its wiring cases carry
mutation arms (flip the job's `if` to `success`, point it at another workflow,
add a `bun install` step) so the checker is proven to detect the known-bad.

## Sovereignty ladder

ADR-029 defines the only supported ways to change upstream behavior:
Expand Down
Loading
Loading