CI:移除本仓非主分支构建能力(潜伏,主 issue xm/yuvu#10) #4

Closed
opened 2026-10-01 16:07:27 +08:00 by xm · 4 comments
Owner

关联

主 issue:xm/yuvu#10(P1)——本 issue 是该根因在本仓的落地项,优先级降为 P2;是否"可关闭"以主 issue 的验收口径为准。

现象

.woodpecker.yml 根 when: 是裸 - event: [push, manual](不含 branch:);docker-publish 无 branch: main 守卫;scripts/ci/publish_image.sh(741B)无条件附加:

  -t "$IMAGE_REPO:latest" \

镜像:docker.lib.zuixue.site/yuvu/cli。

实测证据

近 34 条流水线中非主分支构建 0 条 —— 属潜伏缺陷:能力已具备,只是当前无人推非主分支。修复 lane 一旦推 fix/*,行为与 yuvuNode 今天完全相同(见 xm/yuvu#10)。

推测根因

Woodpecker 3.18.1 的流水线配置是从被推送的那个 ref 上读取的(本仓 repo 设置 config_file 为空 → 走默认路径 .woodpecker/*.{yaml,yml} / .woodpecker.yaml / .woodpecker.yml);服务器侧 repo 对象没有分支白名单字段(仅 pr_enabled / allow_pr / allow_deploy / require_approval / timeout / config_file)。因此 when: 未声明 branch 时,push 事件对任意分支成立;发布侧又缺少分支判定,未合并分支的产物会顶掉被生产或其它仓消费的 tag。

期望(完成定义)

when:
  - event: [push, manual]
    branch: main
  - event: manual        # 任意 ref 手动触发,供分支验证使用;不要删除,见 xm/yuvu#10「与 yuvuAdapter#30 的冲突」

发布侧二选一(仓内均有先例):

  • 步骤级:给发布步骤加 when: { branch: main }(照 yuvuAdapter,非主分支只跑只读门禁)
  • 脚本级:加 should_publish_latest 判定,只在 main 上附加 :latest(照 yuvuCore/scripts/ci/publish_image.sh)

最小复现

  1. 造一次无害提交推到非主分支(如 fix/ci-scope-probe);
  2. 观察:Woodpecker 是否产生流水线、发布步骤是否执行、registry 上相关 tag 的 digest 是否变化。
    (期望:无流水线;或发布步骤 skipped 且 digest 不变。)

验收

见 xm/yuvu#10「验收口径」1–4。

证据获取方式(可复现)

  • 触发条件:GET /api/v1/repos/xm/<repo>/contents/.woodpecker.yml?ref=main
  • 非主分支构建:GET https://woodpecker.nas.zuixue.site/api/repos/<id>/pipelines?page=1&per_page=50
  • 发布实锤:GET /api/repos/<id>/pipelines/<num> → 取发布步骤 child id → GET /api/repos/<id>/logs/<num>/<step_id>
  • 仓 id:yuvuNode=5、yuvuNotify=6、yuvuImages=4、matchsubtitles=8、yuvuCli=9
## 关联 主 issue:**xm/yuvu#10**(P1)——本 issue 是该根因在本仓的落地项,优先级降为 **P2**;是否"可关闭"以主 issue 的验收口径为准。 ## 现象 `.woodpecker.yml` 根 `when:` 是裸 `- event: [push, manual]`(不含 `branch:`);`docker-publish` 无 `branch: main` 守卫;`scripts/ci/publish_image.sh`(741B)无条件附加: ```bash -t "$IMAGE_REPO:latest" \ ``` 镜像:`docker.lib.zuixue.site/yuvu/cli`。 ## 实测证据 近 34 条流水线中**非主分支构建 0 条** —— 属**潜伏缺陷**:能力已具备,只是当前无人推非主分支。修复 lane 一旦推 `fix/*`,行为与 `yuvuNode` 今天完全相同(见 xm/yuvu#10)。 ## 推测根因 Woodpecker `3.18.1` 的流水线配置是**从被推送的那个 ref 上读取**的(本仓 repo 设置 `config_file` 为空 → 走默认路径 `.woodpecker/*.{yaml,yml}` / `.woodpecker.yaml` / `.woodpecker.yml`);服务器侧 repo 对象**没有分支白名单字段**(仅 `pr_enabled / allow_pr / allow_deploy / require_approval / timeout / config_file`)。因此 `when:` 未声明 `branch` 时,push 事件对**任意分支**成立;发布侧又缺少分支判定,未合并分支的产物会顶掉被生产或其它仓消费的 tag。 ## 期望(完成定义) ```yaml when: - event: [push, manual] branch: main - event: manual # 任意 ref 手动触发,供分支验证使用;不要删除,见 xm/yuvu#10「与 yuvuAdapter#30 的冲突」 ``` 发布侧二选一(仓内均有先例): - **步骤级**:给发布步骤加 `when: { branch: main }`(照 `yuvuAdapter`,非主分支只跑只读门禁) - **脚本级**:加 `should_publish_latest` 判定,只在 main 上附加 `:latest`(照 `yuvuCore/scripts/ci/publish_image.sh`) ## 最小复现 1. 造一次无害提交推到非主分支(如 `fix/ci-scope-probe`); 2. 观察:Woodpecker 是否产生流水线、发布步骤是否执行、registry 上相关 tag 的 digest 是否变化。 (期望:无流水线;或发布步骤 `skipped` 且 digest 不变。) ## 验收 见 **xm/yuvu#10**「验收口径」1–4。 ## 证据获取方式(可复现) - 触发条件:`GET /api/v1/repos/xm/<repo>/contents/.woodpecker.yml?ref=main` - 非主分支构建:`GET https://woodpecker.nas.zuixue.site/api/repos/<id>/pipelines?page=1&per_page=50` - 发布实锤:`GET /api/repos/<id>/pipelines/<num>` → 取发布步骤 child id → `GET /api/repos/<id>/logs/<num>/<step_id>` - 仓 id:yuvuNode=5、yuvuNotify=6、yuvuImages=4、matchsubtitles=8、yuvuCli=9
Author
Owner

本轮复核(2026-10-01)

main 的 .woodpecker.yml 仍是裸 - event: [push, manual](发布脚本无 branch 守卫),自 3~7 月起无修改 ⇒ 未修。

对照:yuvuCore、yuvuAdapter 两仓已完成同样收口。修复归属本仓 coding agent。

## 本轮复核(2026-10-01) main 的 `.woodpecker.yml` **仍是裸 `- event: [push, manual]`**(发布脚本无 branch 守卫),自 3~7 月起无修改 ⇒ **未修**。 对照:`yuvuCore`、`yuvuAdapter` 两仓已完成同样收口。修复归属本仓 coding agent。
Author
Owner

总控复核 + 已合并到 main(2026-10-01 18:5x CST)

结论:本仓收口已落地 main,等待真机/CI 验证。

交付

  • 分支 fix/cli-4-ci-publish-scope(3 提交:7c98695 收口 :latest、c75502c make check 接入、3905503 流水线 test 步骤跑新测试)
  • --no-ff 合并入 main:5a1fb2c(父 c4edf5e),已 push origin/main

改动面(git diff --stat c4edf5e..5a1fb2c)

 .woodpecker.yml                  |   9 +++-
 Makefile                         |   7 ++-
 scripts/ci/publish_image.sh      |  12 +++--
 scripts/ci/publish_scope.sh      |  28 +++++++++++
 scripts/ci/test_publish_scope.sh | 106 +++++++++++++++++++++++++++++++
  • .woodpecker.yml:when 由裸 event: [push, manual] 改为 push 仅 branch: main + manual 任意 ref(保留分支真机验证);test 步骤新增 make publish-scope-check
  • 第二道守卫:scripts/ci/publish_image.sh 仅当 should_publish_latest 为真才附加 -t $IMAGE_REPO:latest,否则只推 <UTC date>-<commit7>
  • 判定抽成纯函数 scripts/ci/publish_scope.sh(可 source、可本地直跑,缺省/未知 ref 一律按非 main)

原始证据(总控独立复跑,非转述)

门禁(合并后干净 main 上重跑):

check_vendor.sh -> Go vendor snapshot check passed (rc=0)
check_go_quality.sh -> Go format and vet checks passed (rc=0)
make shell-check -> rc=0
bash scripts/ci/test_publish_scope.sh -> publish scope checks passed (rc=0)
go build ./... -> rc=0
go test -count=1 -p 2 ./... -> all ok (rc=0)
make quality -> Go format and vet checks passed (rc=0)

反向验证(变异 → 必须红):

M1 无条件允许 :latest -> rc=1,FAIL: should_publish_latest('refs/heads/fix/cli-4-ci-publish-scope')=yes want no
M2 无条件抑制        -> rc=1,FAIL: should_publish_latest('refs/heads/main')=no want yes
还原后 sha256 = 800068e70c3850ec2abcf4849e362e996de24ce24ed047b6e14ccbf0dfd44861(与 HEAD 逐位相同),复绿 rc=0

判定函数直跑(无 Woodpecker 环境变量):

refs/heads/main -> publish-latest      refs/heads/some-branch -> suppress-latest
未设 CI_COMMIT_REF -> suppress-latest   显式空 ref -> suppress-latest

流水线侧旁证:本仓近 35 条流水线全部为 refs/heads/main(分支 push 未产生流水线)——与「潜伏未触发」的判断一致。

请验证这几点

  1. 合并后 main 的流水线(5a1fb2c)是否 success,且 test 步骤输出含 publish scope checks passed;
  2. 非 main 分支 push 不再产生流水线;分支上 manual 触发时日志 TRACE publish_image ... latest=suppress、registry 上 yuvu/cli:latest 的 digest 不变;
  3. main push 时 latest=attach,:latest 随 main 前进(本次合并即 5a1fb2c 的构建,可对比 digest);
  4. 与本 issue 的父 issue xm/yuvu#10 验收口径 1–4 对齐后再决定关闭。

未做 / 说明

  • 未在 CI 真跑 publish(合并推 main 后由流水线执行;本地只用 fake docker 做过 hermetic 验证),未手动推/删任何 tag;
  • 任务书提到的 scripts/ci/test_buildx_lib.sh 在本仓不存在(ls 报 no such file),未凭空新建;
  • ROUND-NOTES-CL4a.md / TASKD-CL4a.md 保持未跟踪(遵 yuvuCore#30)。

(总控 · 2026-10-01 18:5x CST)

## 总控复核 + 已合并到 main(2026-10-01 18:5x CST) **结论:本仓收口已落地 main,等待真机/CI 验证。** ### 交付 - 分支 `fix/cli-4-ci-publish-scope`(3 提交:`7c98695` 收口 `:latest`、`c75502c` make check 接入、`3905503` 流水线 test 步骤跑新测试) - `--no-ff` 合并入 main:**`5a1fb2c`**(父 `c4edf5e`),已 push `origin/main` ### 改动面(`git diff --stat c4edf5e..5a1fb2c`) ``` .woodpecker.yml | 9 +++- Makefile | 7 ++- scripts/ci/publish_image.sh | 12 +++-- scripts/ci/publish_scope.sh | 28 +++++++++++ scripts/ci/test_publish_scope.sh | 106 +++++++++++++++++++++++++++++++ ``` - `.woodpecker.yml`:`when` 由裸 `event: [push, manual]` 改为 `push` **仅 `branch: main`** + `manual` 任意 ref(保留分支真机验证);test 步骤新增 `make publish-scope-check` - 第二道守卫:`scripts/ci/publish_image.sh` 仅当 `should_publish_latest` 为真才附加 `-t $IMAGE_REPO:latest`,否则只推 `<UTC date>-<commit7>` - 判定抽成纯函数 `scripts/ci/publish_scope.sh`(可 source、可本地直跑,缺省/未知 ref 一律按非 main) ### 原始证据(总控独立复跑,非转述) 门禁(合并后干净 main 上重跑): ``` check_vendor.sh -> Go vendor snapshot check passed (rc=0) check_go_quality.sh -> Go format and vet checks passed (rc=0) make shell-check -> rc=0 bash scripts/ci/test_publish_scope.sh -> publish scope checks passed (rc=0) go build ./... -> rc=0 go test -count=1 -p 2 ./... -> all ok (rc=0) make quality -> Go format and vet checks passed (rc=0) ``` 反向验证(变异 → 必须红): ``` M1 无条件允许 :latest -> rc=1,FAIL: should_publish_latest('refs/heads/fix/cli-4-ci-publish-scope')=yes want no M2 无条件抑制 -> rc=1,FAIL: should_publish_latest('refs/heads/main')=no want yes 还原后 sha256 = 800068e70c3850ec2abcf4849e362e996de24ce24ed047b6e14ccbf0dfd44861(与 HEAD 逐位相同),复绿 rc=0 ``` 判定函数直跑(无 Woodpecker 环境变量): ``` refs/heads/main -> publish-latest refs/heads/some-branch -> suppress-latest 未设 CI_COMMIT_REF -> suppress-latest 显式空 ref -> suppress-latest ``` 流水线侧旁证:本仓近 35 条流水线**全部为 `refs/heads/main`**(分支 push 未产生流水线)——与「潜伏未触发」的判断一致。 ### 请验证这几点 1. 合并后 main 的流水线(`5a1fb2c`)是否 success,且 `test` 步骤输出含 `publish scope checks passed`; 2. 非 main 分支 push **不再产生流水线**;分支上 manual 触发时日志 `TRACE publish_image ... latest=suppress`、registry 上 `yuvu/cli:latest` 的 digest **不变**; 3. main push 时 `latest=attach`,`:latest` 随 main 前进(本次合并即 `5a1fb2c` 的构建,可对比 digest); 4. 与本 issue 的父 issue **xm/yuvu#10** 验收口径 1–4 对齐后再决定关闭。 ### 未做 / 说明 - 未在 CI 真跑 publish(合并推 main 后由流水线执行;本地只用 fake docker 做过 hermetic 验证),未手动推/删任何 tag; - 任务书提到的 `scripts/ci/test_buildx_lib.sh` 在本仓**不存在**(`ls` 报 no such file),未凭空新建; - `ROUND-NOTES-CL4a.md` / `TASKD-CL4a.md` 保持未跟踪(遵 yuvuCore#30)。 (总控 · 2026-10-01 18:5x CST)
Author
Owner

验证结果(2026-10-01 20:0x CST · 验证方):修复已在 main,但 CI 拿不到证据 → 不关闭

① 你请验证的第 1 条(5a1fb2c 流水线 success + test 步骤含 publish scope checks passed):❌ 不存在
Woodpecker repo id=9 最新流水线仍是 #34(c4edf5e,2026-09-23);5a1fb2c(18:49 推送)之后没有任何流水线。

根因不在本仓代码,而在 CI 镜像停滞:repo 9 的 clone_url = https://forgejo.nas.zuixue.site/zx/yuvuCli.git,该镜像 main 仍为 c4edf5e,上游 xm/yuvuCli 已是 5a1fb2c;同一时刻 zx/yuvuNode 镜像已同步当日推送并触发 #147(对照组成立)。已单开 xm/yuvuAdapter#34。

② 你请验证的第 3 条(main push → latest=attach、:latest 随 main 前进):❌ 未发生
registry yuvu/cli 的 tag 列表最新仍是 20260922-*,没有 20261001-5a1fb2c;:latest 的 index digest f919c793… 与 20260922-c4edf5e 的 digest 逐位相同(未变)。修复镜像从未被发布。

③ 我能独立核实的部分(本地,非 CI)

  • wt/yuvuCli(HEAD 5a1fb2c,git status 干净)跑 bash scripts/ci/test_publish_scope.sh → publish scope checks passed,rc=0;sha256(publish_scope.sh)=800068e70c38… 与你给的基线一致。
  • 直接 source 判定函数逐 ref 实测:refs/heads/main → attach;refs/heads/fix/probe → suppress;refs/heads/main-x → suppress;空 ref → suppress;未设 CI_COMMIT_REF → suppress。
  • main 的 .woodpecker.yml 根 when: = - event: push, branch: main + - event: manual(不限分支);make publish-scope-check 已进 test 步骤。

④ 你请验证的第 2 条(非 main manual → TRACE latest=suppress 且 :latest digest 不变):未做
我不会为验证去触发一次 manual 流水线:那会向生产 registry 推一个新的日期 tag(不可逆的写操作),且在镜像停滞下也触发不了。

结论:修复内容在 main 上成立、本地可复核;但闭环所需的 CI 证据(①③)缺失,且缺失原因在本仓之外(镜像同步)。按契约不关闭,标签保持 needs-fix。等 zx/yuvuCli 镜像恢复同步(yuvuAdapter#34)后复核 ①②③ 再决定关闭。

未验证到:任何 CI 真跑证据(test 步骤输出、latest=attach/suppress 的 TRACE 行)、5a1fb2c 的镜像是否可发布。

## 验证结果(2026-10-01 20:0x CST · 验证方):修复已在 main,**但 CI 拿不到证据 → 不关闭** **① 你请验证的第 1 条(`5a1fb2c` 流水线 success + test 步骤含 `publish scope checks passed`):❌ 不存在** Woodpecker repo id=9 最新流水线仍是 **#34(`c4edf5e`,2026-09-23)**;`5a1fb2c`(18:49 推送)之后**没有任何流水线**。 根因不在本仓代码,而在 **CI 镜像停滞**:repo 9 的 `clone_url = https://forgejo.nas.zuixue.site/zx/yuvuCli.git`,该镜像 main 仍为 `c4edf5e`,上游 `xm/yuvuCli` 已是 `5a1fb2c`;同一时刻 `zx/yuvuNode` 镜像已同步当日推送并触发 #147(对照组成立)。已单开 **xm/yuvuAdapter#34**。 **② 你请验证的第 3 条(main push → `latest=attach`、`:latest` 随 main 前进):❌ 未发生** registry `yuvu/cli` 的 tag 列表最新仍是 `20260922-*`,没有 `20261001-5a1fb2c`;`:latest` 的 index digest `f919c793…` 与 `20260922-c4edf5e` 的 digest **逐位相同**(未变)。修复镜像从未被发布。 **③ 我能独立核实的部分(本地,非 CI)** - `wt/yuvuCli`(HEAD `5a1fb2c`,`git status` 干净)跑 `bash scripts/ci/test_publish_scope.sh` → `publish scope checks passed`,rc=0;`sha256(publish_scope.sh)=800068e70c38…` 与你给的基线一致。 - 直接 `source` 判定函数逐 ref 实测:`refs/heads/main → attach`;`refs/heads/fix/probe → suppress`;`refs/heads/main-x → suppress`;空 ref → suppress;未设 CI_COMMIT_REF → suppress。 - main 的 `.woodpecker.yml` 根 `when:` = `- event: push, branch: main` + `- event: manual`(不限分支);`make publish-scope-check` 已进 test 步骤。 **④ 你请验证的第 2 条(非 main manual → TRACE `latest=suppress` 且 `:latest` digest 不变):未做** 我不会为验证去触发一次 manual 流水线:那会向生产 registry 推一个新的日期 tag(不可逆的写操作),且在镜像停滞下也触发不了。 **结论**:修复内容在 main 上成立、本地可复核;但**闭环所需的 CI 证据(①③)缺失,且缺失原因在本仓之外**(镜像同步)。按契约**不关闭**,标签保持 `needs-fix`。等 `zx/yuvuCli` 镜像恢复同步(yuvuAdapter#34)后复核 ①②③ 再决定关闭。 **未验证到**:任何 CI 真跑证据(test 步骤输出、`latest=attach/suppress` 的 TRACE 行)、`5a1fb2c` 的镜像是否可发布。
Author
Owner

验证方复核(真产物 + 真机证据)→ 通过,关闭

修复在 main、镜像已发布且 :latest 已指向该构建;上一轮卡住的「CI 镜像同步停滞」本轮已解除。

  1. 代码面:5a1fb2c 是 origin/main 祖先,main 头 = c14d8b7。.woodpecker.yml(@c14d8b7)根 when = - event: push / branch: main + - event: manual;test 步骤含 make shell-check、make publish-scope-check。

  2. CI:Woodpecker repo 9 流水线 #35 success @ c14d8b7cf(main/push),clone/test/race/binary-build/docker-publish 五步全部 exit 0。

  3. 发布作用域真机证据(docker-publish 日志):
    TRACE publish_image image=docker.lib.zuixue.site/yuvu/cli tag=20261001-c14d8b7 ref=refs/heads/main latest=attach platforms=linux/amd64,linux/arm64
    该 TRACE 的 latest=attach 由 scripts/ci/publish_scope.sh 的 should_publish_latest() 判定(仅 ref==main 才 attach);随后 exporting manifest list sha256:8af080d5…、pushing manifest for …/yuvu/cli:latest@sha256:8af080d5…。

  4. 注册表身份:tag 20261001-c14d8b7 与 :latest 的 index digest 同为 sha256:8af080d5a3c80c1be38bd249c611731b804e1d56e58ab8e91293ba898ccabf6b;修复前 :latest = sha256:f919c793…(= tag 20260922-c4edf5e)⇒ :latest 已由本修复的构建推进。

  5. 镜像停滞解除:CI 镜像 zx/yuvuCli main 现为 c14d8b7cf111(上一轮仍停 c4edf5e,约 9 天),故 #35 真正触发(对照 zx/yuvuNode 同期正常)。

没验证到的(如实写)

  • 非 main ref → latest=suppress 端到端:需一次分支 push / registry 写,不在验证方权限内;仓库自带 scripts/ci/test_publish_scope.sh 覆盖该判定(本地复跑通过)。
  • #35 的 test 步骤日志经 log API 读取有断档(行 3–8 缺失;step exit=0),无法逐字引用 publish scope checks passed 一行;该判定确实执行由上面 docker-publish 的 TRACE 反证。
**验证方复核(真产物 + 真机证据)→ 通过,关闭** 修复在 main、镜像已发布且 `:latest` 已指向该构建;上一轮卡住的「CI 镜像同步停滞」本轮已解除。 1. 代码面:`5a1fb2c` 是 `origin/main` 祖先,main 头 = `c14d8b7`。`.woodpecker.yml`(@c14d8b7)根 when = `- event: push / branch: main` + `- event: manual`;test 步骤含 `make shell-check`、`make publish-scope-check`。 2. CI:Woodpecker repo 9 流水线 **#35 success @ `c14d8b7cf`**(main/push),clone/test/race/binary-build/docker-publish 五步全部 exit 0。 3. 发布作用域真机证据(docker-publish 日志): `TRACE publish_image image=docker.lib.zuixue.site/yuvu/cli tag=20261001-c14d8b7 ref=refs/heads/main latest=attach platforms=linux/amd64,linux/arm64` 该 TRACE 的 `latest=attach` 由 `scripts/ci/publish_scope.sh` 的 `should_publish_latest()` 判定(仅 ref==main 才 attach);随后 `exporting manifest list sha256:8af080d5…`、`pushing manifest for …/yuvu/cli:latest@sha256:8af080d5…`。 4. 注册表身份:tag `20261001-c14d8b7` 与 `:latest` 的 index digest **同为 `sha256:8af080d5a3c80c1be38bd249c611731b804e1d56e58ab8e91293ba898ccabf6b`**;修复前 `:latest` = `sha256:f919c793…`(= tag `20260922-c4edf5e`)⇒ `:latest` 已由本修复的构建推进。 5. 镜像停滞解除:CI 镜像 `zx/yuvuCli` main 现为 `c14d8b7cf111`(上一轮仍停 `c4edf5e`,约 9 天),故 #35 真正触发(对照 `zx/yuvuNode` 同期正常)。 **没验证到的(如实写)** - 非 main ref → `latest=suppress` 端到端:需一次分支 push / registry 写,不在验证方权限内;仓库自带 `scripts/ci/test_publish_scope.sh` 覆盖该判定(本地复跑通过)。 - #35 的 test 步骤日志经 log API 读取有断档(行 3–8 缺失;step exit=0),无法逐字引用 `publish scope checks passed` 一行;该判定确实执行由上面 docker-publish 的 TRACE 反证。
xm 2026-10-01 21:37:42 +08:00
Sign in to join this conversation.
No description provided.