Skip to content

状态扫描:README 落后两批 —— ★ 而落后的正是「挡不挡上线」那一句 #14

状态扫描:README 落后两批 —— ★ 而落后的正是「挡不挡上线」那一句

状态扫描:README 落后两批 —— ★ 而落后的正是「挡不挡上线」那一句 #14

Workflow file for this run

# 门禁(2026-08-21 批 13,owner 拍板:GitHub Actions 跑**全量**)
#
# ★ ★ ★ **这里只有一条命令,而且与本机那条逐字相同**:`bash tests/m0/docker-run.sh`。
# 这不是省事,是判据纪律:CI 若自己拼一套「等价的」步骤,就会长出**第二套判据**,
# 而两套判据迟早分家 —— 那时「CI 绿」与「门禁绿」是两件事,
# 却没有任何东西会说出来。本仓库为这个形状栽过好几次。
#
# ⚠ ⚠ **有意不写 `paths-ignore`。** 「只改文档就跳过 CI」看起来是白捡的省钱,
# 实际是错的:`crates/fulcrum-config/tests/doc_contract.rs` 用 `include_str!`
# 读 `docs/architecture/dsl-reference.md`,`crates/fulcrum/tests/supply_gates.rs`
# 读根 `Cargo.toml`/`Cargo.lock`,而批 11 那道门还反向断言
# **已接线的能力不许出现在 `dsl-reference.md` 里**。
# ⇒ **一次纯文档改动是真的能把门禁弄红的**,跳过它就等于把这类缺陷放行,
# 而现象是「CI 一直绿」。省钱要从别处省(见下面的 concurrency 与 timeout)。
#
# ⚠ **本仓库是私有仓库 ⇒ Actions 分钟数是计费的。** 两条止血阀都在下面:
# · `concurrency` + `cancel-in-progress`:连着推几次时,只有最后一次跑到底;
# · `timeout-minutes`:卡死的任务最多烧这么多,而不是默认的 6 小时。
name: gate
on:
# ★ 只在 main 上跑:owner 的口径是「直接提交 main,不开分支不开 PR」。
push:
branches: [main]
# 手工触发,用于改完 workflow 自身之后重跑。
workflow_dispatch:
concurrency:
group: gate-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
gate:
# ★ 钉住大版本,不用 `ubuntu-latest`:M1 的 systemd 场景依赖宿主机是 **cgroup v2**,
# 而 `ubuntu-latest` 换代时不会有任何一行输出告诉我们环境变了。
# 与 G36 钉构建镜像、Dockerfile.systemd 钉 systemd 版本是同一条纪律。
runs-on: ubuntu-24.04
# 冷缓存下要整编 pingora fork + 全部依赖,再跑十四个场景。
# ⚠ 这个上限是**计费止血阀**,不是预期耗时;真实耗时见每次运行的记录。
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
# ★ ★ runner 上 `/` 只有二十几 GB 空余,而这里要放:rust 构建镜像、
# systemd 测试镜像、cargo registry、**release 模式的整个 target**。
# ⚠ 磁盘满的报错形态五花八门(链接器 OOM、docker 写层失败),
# 而没有一条会说「磁盘满了」。先腾出来,比事后猜便宜。
- name: 腾出磁盘
run: |
echo "── 清理前 ──"; df -h /
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/usr/local/share/boost /usr/local/.ghcup "$AGENT_TOOLSDIRECTORY" || true
echo "── 清理后 ──"; df -h /
# ★ 每次都把环境打出来。§8 要求可复现,而可复现的第一步是**看得见跑的是什么**。
# ⚠ cgroup 那一行是判据相关的:M1 的 `ExitType=cgroup` 要求容器侧 systemd ≥ 250
# 且宿主机是 cgroup v2 统一层级。它变成 v1 的那天,M1 场景会以一种
# 看起来像产品缺陷的方式红。
- name: 环境快照
run: |
echo "── docker ──"; docker version
echo "── 内核 ──"; uname -a
echo "── cgroup 层级 ──"; stat -fc %T /sys/fs/cgroup
echo "── git 行尾配置(CRLF 门的前提)──"
git config --get core.autocrlf || echo "core.autocrlf 未设(Linux 上的默认,正是要的)"
# ── 构建缓存(owner 2026-08-21 拍板加)─────────────────────────────
#
# ★ 实测数字(本机量的):`fulcrum-cargo` 684MB、`fulcrum-target-*` **6.3GB**
# (vendor 3.0G + debug 2.8G + release 580M);抽样压缩比 gzip -1 已有 2.8x,
# zstd 更高 ⇒ 归档约 2GB 上下,装得进 GitHub 每仓 10GB 的缓存预算。
# ★ 冷缓存下一次全量 CI 实测 **21m44s**(32441478101,第一次全绿的那次)。
#
# ⚠ ⚠ **缓存最坏的失效方式不是「没命中」,是「命中了一份不该用的」。**
# 两道防线:① 键里带 `Dockerfile.build` 的哈希(工具链身份,连 C 工具链一起);
# ② `tests/ci/cache.sh` 在归档里另存一份 meta,**灌回前逐字比对**,
# 对不上就跳过并说明,绝不硬灌。
#
# ★ **怀疑缓存的时候怎么办**:把下面键里的 `v1` 改成 `v2`,整片缓存立即作废。
- name: 取构建缓存
id: buildcache
uses: actions/cache/restore@v4
with:
path: ~/fulcrum-cache
# ★ 精确键 = 工具链 + 两把锁。锁变了就换一份新缓存,
# 而 restore-keys 让它仍然能从**同一套工具链**的上一份热起来(cargo 增量补差)。
key: fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}-${{ hashFiles('Cargo.lock', 'vendor/pingora/Cargo.lock') }}
restore-keys: |
fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}-
- name: 把缓存灌回 docker 卷
run: bash tests/ci/cache.sh restore ~/fulcrum-cache
# ★ ★ ★ 就这一条。与 AGENTS.md「Current validation」第 3 条、
# 与每份交接单里写的那条命令,**逐字相同**。退出码就是判据。
# ⚠ 绝不接管道:`| tee` / `| tail` 取回的是右边那个命令的退出码(本仓库栽过)。
- name: 全量门禁(十四场景)
run: bash tests/m0/docker-run.sh
# ★ `always()`:门禁红了也要存 —— 依赖照样是编好的,下一次能省下同样多的时间。
# ⚠ 而**精确命中时不重存**(`cache-hit != 'true'`):那 2GB 上传是纯浪费。
# ★ ★ ★ **诊断必须落到「日志之外」的地方。** 2026-08-21 连着两轮都栽在这一步上,
# 而两轮的日志 blob **都取不回来**(`gh run view --log` 一直 404 BlobNotFound)——
# 第一轮是 runner 崩了没上传,第二轮明明成功却也取不到。
# 于是「加了 df 让下一轮自己说话」这个办法**本身就没生效**:
# 话是说了,只是没人听得见。
# ⇒ 诊断写进文件并**当成 artifact 传上去**,那条路与日志是分开的。
# ⚠ ⚠ **精确命中时整步跳过。** 2026-08-21 实测(命中轮 32450933715 的逐步耗时):
# 这一步花了 **368 秒**压出一个 1.7GB 的归档,而下面「存构建缓存」因为
# `cache-hit == 'true'` **被跳过** —— 也就是说那 6 分钟压出来的东西**原样扔掉了**。
# ★ 一步只在「它的产物会被用到」时才该跑;判据要往前挪到这里,
# 而不是等到最后一步再去决定要不要用。
#
# ★ 代价说在明处:命中时缓存**不刷新**。于是随着提交累积,缓存里那份
# target 相对 HEAD 会越来越旧,cargo 每轮补的差量慢慢变大。
# 这是有意的取舍 —— 哪天两把锁之一变了,键跟着变、自然重做一份新的。
- name: 导出构建缓存
id: dumpcache
if: always() && steps.buildcache.outputs.cache-hit != 'true'
run: |
DIAG=~/cache-diag.txt
: > "$DIAG"
say() { echo "$@" | tee -a "$DIAG"; }
say "── 导出前的磁盘 ──"
df -h / 2>&1 | tee -a "$DIAG"
say "── docker 占了多少 ──"
docker system df 2>&1 | tee -a "$DIAG" || true
# ★ 门禁已经跑完了,镜像这时候一个都不需要留:它们本来就不跨轮缓存,
# 每一轮都要重建。搬运用的 helper 会被 cache.sh 重新拉(几十 MB)。
# ⚠ 这一步不影响任何判据,纯粹是腾地方。
say "── 清掉 builder 缓存与所有镜像 ──"
docker builder prune -af >/dev/null 2>&1 || true
docker image prune -af >/dev/null 2>&1 || true
df -h / 2>&1 | tee -a "$DIAG"
# ⚠ ⚠ **判据取「命令成功了没有」,不取「文件在不在」。**
# 2026-08-21 实测:一次 cancel-in-progress 把门禁掐了,导出随之失败,
# **而半截归档已经落盘**(370MB,完整的是 2.2GB)——
# 按「文件在不在」判会把这份残档存进缓存键,而缓存条目不可变,
# 此后每一轮都精确命中它。**「文件存在」不是「导出成功」。**
say "── cache.sh save ──"
if bash tests/ci/cache.sh save ~/fulcrum-cache 2>&1 | tee -a "$DIAG"; then
SAVE_RC=0
else
SAVE_RC=1
fi
# ⚠ 管道里 `tee` 的退出码会盖掉左边那个(本仓库栽过)。取 PIPESTATUS。
SAVE_RC=${PIPESTATUS[0]:-$SAVE_RC}
say "cache.sh save 退出码:$SAVE_RC"
ls -la ~/fulcrum-cache 2>&1 | tee -a "$DIAG" || true
if [ "$SAVE_RC" = "0" ] && [ -f ~/fulcrum-cache/fulcrum-build-cache.tar.z ]; then
echo "archived=true" >> "$GITHUB_OUTPUT"
say "archived=true"
else
echo "archived=false" >> "$GITHUB_OUTPUT"
say "★ archived=false —— 不存缓存(宁可下一轮冷跑,也不占住那个键)"
fi
say "── 导出后的磁盘 ──"
df -h / 2>&1 | tee -a "$DIAG"
# 同一份诊断也塞进 job summary,两条路都留着。
{ echo '```text'; cat "$DIAG"; echo '```'; } >> "$GITHUB_STEP_SUMMARY" || true
# ★ 与日志分开的那条路:artifact 一定取得回来(`gh run download`)。
- name: 传诊断
if: always()
uses: actions/upload-artifact@v4
with:
name: cache-diag
path: ~/cache-diag.txt
if-no-files-found: warn
retention-days: 7
- name: 存构建缓存
if: always() && steps.dumpcache.outputs.archived == 'true' && steps.buildcache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ~/fulcrum-cache
key: fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}-${{ hashFiles('Cargo.lock', 'vendor/pingora/Cargo.lock') }}