mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
2026-10-21 的 main 流水线(dfc4e16)里,「前置检查 gitcode 目标仓是否存在」 首次执行时 gitcode 没有给出预期响应,一次 ls-remote 判失败 ⇒ sync job 红; 重跑立刻全绿。把一次网络抖动翻译成「镜像同步坏了」,是纯噪声。 改法:只对**网络层**失败重试,其余立即失败。 - 网络层(DNS/连接重置/TLS/超时/EOF)→ 最多 3 次,退避 5s、10s - 仓不存在(gitcode 实测:could not be found + **403**,不是 404) → 立即失败,不重试。重试只会把「去建仓」拖成十几秒空等 - 凭据问题(401/403 但不带 not-found 特征)→ 立即失败,不重试 (token 错了等 15 秒也不会变对) - 分类顺序上 notfound 必须先于 403:gitcode 的「仓不存在」响应里 **同时**含 could not be found 与 error: 403,先判 403 会把真问题贴上 「凭据问题」的错误标签 - 每次尝试打一行「第 N/M 次探测失败(类别):摘要」;重试提示打到 stderr (⏳ … 秒后重试),Actions 日志里一眼看得出发生过重试 - 耗尽重试时整段回放每次尝试的摘要,并明确写「判定:网络问题」 —— 与「仓不存在」/「凭据问题」的判定措辞互不相同,可直接 grep 区分 同口径:**抽成 .github/scripts/check-gitcode-repo.sh,两个 workflow 共用同一个 文件**,而不是两边各写一份「逐字一致」的脚本 —— 后者迟早会漂。ci.yml 的 sync-gitcode-main 与 release.yml 的 sync-gitcode 各留 1 行 bash 调用。 (抽得出公共片段,故没有采用「两边逐字改」的方案。) token 安全:GC_URL 含 token,永不 echo;所有输出先过 redact()。脱敏两道: 先按字面量遮 token(BRE 元字符先转义),再按 URL 结构遮 userinfo@。 结构式的 password 段**不能排除 `/`** —— 真实 token 可以含 `/`,第一版写成 [^@/[:space:]]* 时 `https://user:tok/en@host` 匹配不上,只剩字面量一道兜住 (判据 C6 首次就是红的)。 判据与变异(9 组正向 / 8 条变异,全部咬住): 判据脚本与变异脚本见 commondev 台账 criteria/b7-gitcode-precheck-retry{,.mutations}.sh。 正向覆盖:重试次数上限(恰好 3 次探测)、仓不存在时 1 次即失败无重试痕迹、 首次成功无重试噪声、抖动后第 2 次成功、凭据问题不重试、token 不进日志、 默认退避真的是 5s/10s(用假 sleep 记录入参,不真等 15 秒)、 两处 workflow 都调同一脚本事 yaml 可解析 + bash -n。 变异 8/8 咬住:默认次数 3→1、退避→0、仓不存在也重试、去掉上限、 去掉重试提示、清空 notfound 特征串、去掉结构式脱敏、ci.yml 退回内联 ls-remote。 过程中修掉三处自己判据的盲区(都不是改产物去迁就判据,而是补判据): 1. M2(退避→0/0)首次未被咬住 ⇒ 补 C7「默认退避 = 5 10」(假 sleep 记录入参) 2. M7(删结构式脱敏)首次未被咬住 ⇒ 补 C7b:userinfo 密码用**别的** secret 且**含 `/`**。C6 用当前 token 的字面量做输入,字面量替换本身就能抹掉, 于是结构式规则删了也照样绿;且密码不含 `/` 时兜底的裸 user@ 规则会顶上, 同样验不出来 3. C7b 第一版断言 grep 'JianFeeeee' 会误报 —— 脚本正常输出里就有 gitcode.com/JianFeeeee/HologramAgent(那是路径不是凭据),改为断言 userinfo 形态(://JianFeeeee[:@]) 真实网络实测(不是只跑假 git): - 仓存在:目标仓可访问,rc=0 - 仓不存在:1 次探测即失败,日志含 could not be found + 403,rc=1 - 网络失败:3 次探测 + 2 次退避,总耗时 5.7s(退避调成 1s/1s),rc=1 yaml.safe_load 三个 workflow 全部可解析;bash -n 通过。 未改动(留待裁决):deploy/scripts/upload_assets.py docstring 里 「SDK 仓传 JianFeeeee/homeagent-sdk」指 gitcode 侧、与实测一致 (gitcode: homeagent-sdk=200 / homeagentsdk=404;GitHub 相反),保持原样。
277 lines
11 KiB
YAML
277 lines
11 KiB
YAML
# HomeAgent 主仓 CI。
|
||
#
|
||
# 设计原则:**CI 里跑的每一条命令,都是本地已实测通过的命令**。
|
||
# 不写「应该有用来试试」的步骤 —— 未验证的 CI 步骤会把假红灯变成常态,
|
||
# 最后所有人学会忽略它。
|
||
#
|
||
# 覆盖范围与本地 `make test` 对齐(build / vet / test / client-versions /
|
||
# gui / csrc),并按依赖拆成独立 job,便于失败定位。
|
||
#
|
||
# 明确**不在** CI 里跑的东西(依赖真机/密钥/内网,跑了只会变 flaky 噪音):
|
||
# - deploy-*.sh / homed 生产部署
|
||
# - waiter 真机验证(192.168.2.x)
|
||
# - cmd/gui 的 `npm run test-live`(需真 Electron + Xvfb + 真后端)
|
||
# - scripts/kernel-stress/*(需 llmsproxy 与压测端点)
|
||
# - 需要 DEEPSEEK_API_KEY / MEDIALIVE_* 的真实 LLM 测试(已自带 t.Skip)
|
||
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: [main, 'release/**']
|
||
pull_request:
|
||
workflow_dispatch:
|
||
|
||
# 只读权限:CI 不需要写仓库。
|
||
permissions:
|
||
contents: read
|
||
|
||
# 同一分支连续推送时取消旧跑,省额度也避免过期结果误导。
|
||
concurrency:
|
||
group: ci-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
# gojieba / onnx 相关包需要 cgo ⇒ 不能用 CGO_ENABLED=0。
|
||
CGO_ENABLED: 1
|
||
# 减少 go test 输出噪音。
|
||
GOFLAGS: -buildvcs=false
|
||
|
||
jobs:
|
||
# ── Go 后端:构建 + 静态检查 + 全量测试 + 跨平台客户端版本一致性 ──
|
||
go:
|
||
name: Go build / vet / test
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 30
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
|
||
- uses: actions/setup-go@v7
|
||
with:
|
||
go-version-file: go.mod
|
||
cache: true
|
||
|
||
# cgo 需要 gcc/g++(gojieba 会编译自带 C++ 源码)。
|
||
- name: 确认 cgo 工具链
|
||
run: |
|
||
gcc --version | head -1
|
||
g++ --version | head -1
|
||
|
||
- name: go build ./...
|
||
run: go build ./...
|
||
|
||
- name: go vet ./...
|
||
run: go vet ./...
|
||
|
||
# ./... 不点名 cmd/gui(该目录是纯 Electron,无 .go 文件):
|
||
# 显式 `go test ./cmd/gui` 会报 "no Go files",那是误报,不是缺陷。
|
||
- name: go test ./...
|
||
run: go test ./... -count=1 -timeout 20m
|
||
|
||
# 跨平台客户端版本一致性:内核 internal/meta 是唯一事实源,
|
||
# GUI(package.json) / 鸿蒙(AppScope/app.json5) / waiter 都必须跟它一致。
|
||
- name: 客户端版本一致性
|
||
run: make check-client-versions
|
||
|
||
# ── 竞态检测(并发改动的主要防线)──
|
||
race:
|
||
name: Race detector
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 20
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
- uses: actions/setup-go@v7
|
||
with:
|
||
go-version-file: go.mod
|
||
cache: true
|
||
- name: go test -race(并发核心)
|
||
run: |
|
||
go test -race \
|
||
./internal/agent/core/ ./cmd/waiter/ \
|
||
-count=1 -timeout 15m
|
||
|
||
# ── 交叉编译:可在无 cgo 下构建的客户端/工具 ──
|
||
#
|
||
# 只有这三个 cmd 支持纯交叉编译。另外三个依赖 cgo(gojieba / onnx):
|
||
# homed / memgc / homed-kb-migrate → internal/memory(gojieba)
|
||
# homed → internal/agent/api(onnx)
|
||
# 它们必须在原生平台构建(见 Makefile 的 build target)。
|
||
cross:
|
||
name: Cross-compile
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 20
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- goos: linux
|
||
goarch: amd64
|
||
ext: ""
|
||
- goos: linux
|
||
goarch: arm64
|
||
ext: ""
|
||
- goos: darwin
|
||
goarch: amd64
|
||
ext: ""
|
||
- goos: darwin
|
||
goarch: arm64
|
||
ext: ""
|
||
- goos: windows
|
||
goarch: amd64
|
||
ext: ".exe"
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
- uses: actions/setup-go@v7
|
||
with:
|
||
go-version-file: go.mod
|
||
cache: true
|
||
- name: 构建 ${{ matrix.goos }}/${{ matrix.goarch }}
|
||
env:
|
||
GOOS: ${{ matrix.goos }}
|
||
GOARCH: ${{ matrix.goarch }}
|
||
CGO_ENABLED: 0
|
||
run: |
|
||
set -euo pipefail
|
||
mkdir -p dist
|
||
for c in waiter initconfig mock-server; do
|
||
out="dist/${c}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.ext }}"
|
||
go build -trimpath -o "$out" "./cmd/${c}"
|
||
echo " ✓ ${c} ${{ matrix.goos }}/${{ matrix.goarch }}"
|
||
done
|
||
|
||
# ── Electron GUI(纯 Node 测试,零依赖)──
|
||
#
|
||
# `npm test` 跑五个 .mjs,全部只 import node: 内置模块(fs/url/path/vm),
|
||
# 所以**不需要 npm ci、不需要 electron**,秒级完成。
|
||
# `npm run test-live` 需真 Electron + 真后端 ⇒ 不进 CI。
|
||
#
|
||
# endpoint-align.test.mjs 跨出 cmd/gui 读 ../../internal/plugins/webui/handler.go
|
||
# 做端点对账 —— checkout 深度足够(默认 fetch-depth:1 已含工作树文件),
|
||
# 不需要额外 checkout 步骤。
|
||
#
|
||
# connections-io.test.mjs 同样是沙箱里跑 main.js 的**真实函数**(node:vm),
|
||
# 在临时目录里断言连接配置的加载/保存行为,不碰真实用户数据。
|
||
gui:
|
||
name: GUI (node)
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
- uses: actions/setup-node@v7
|
||
with:
|
||
node-version: '22'
|
||
- name: npm test
|
||
working-directory: cmd/gui
|
||
run: npm test
|
||
|
||
# ── C 基础设施门禁(ABI / 告警 / ASan+UBSan / 跨架构)──
|
||
csrc:
|
||
name: C infrastructure gates
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 20
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
|
||
# clang 供双编译器告警对照;gcc-aarch64 供跨架构编译门禁。
|
||
# 门禁在缺工具时是显式 SKIP 而不是假通过,这里装齐以免静默降级。
|
||
- name: 安装 C 工具链
|
||
run: |
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y -qq cmake clang gcc-aarch64-linux-gnu
|
||
|
||
- name: make check-csrc
|
||
run: make check-csrc
|
||
|
||
# ── 文档站构建(mkdocs,纯 Python,无外部依赖)──
|
||
docs:
|
||
name: Docs build
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
- uses: actions/setup-python@v7
|
||
with:
|
||
python-version: '3.12'
|
||
- name: 校验站点配置可解析
|
||
# 这里只做「配置与文档源没坏」的轻量校验,不做完整 mkdocs build
|
||
# (站点发布有独立流水线,见 deploy/scripts/deploy-sdk-site.sh)。
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -f mkdocs.yml ]; then
|
||
python -c \
|
||
"import yaml; yaml.safe_load(open('mkdocs.yml'))" \
|
||
&& echo "mkdocs.yml OK"
|
||
else
|
||
echo "无 mkdocs.yml,跳过"
|
||
fi
|
||
test -d docs || echo "无 docs/,跳过"
|
||
|
||
# ── gitcode 镜像同步(main 分支)──
|
||
#
|
||
# 背景(2026-10-06 审计):gitcode 的定位是**只读镜像**(ci-cd-runbook §0),
|
||
# 但既有 sync job 只在 release.yml 里推 **tag** —— main 分支从不同步,
|
||
# 导致镜像的 main 停在 09-29、与主仓分叉 768/746(当时已 force push 对齐一次)。
|
||
#
|
||
# 本 job 把「镜像 = 主仓」交给 CI 保证:main 每次推送且**全部必需检查通过后**
|
||
# 才快进镜像分支 —— 红的代码不进镜像,镜像永远等于主仓。
|
||
#
|
||
# - 只处理 main(feature/release 分支不镜像,与 tag 语义一致)。
|
||
# - 未配置 GITCODE_TOKEN 时显式跳过(与 release.yml 的 sync-gitcode 同口径,
|
||
# 不阻断 GitHub 侧 CI)。
|
||
# - 用快进 push(非 force):正常流水线下镜像只可能落后,不可能领先;
|
||
# 若出现非快进说明有人动过镜像,此时响亮失败而不是悄悄覆盖 ——
|
||
# 镜像被旁路写入是「单向性被破坏」的信号,值得一次人工确认。
|
||
sync-gitcode-main:
|
||
name: Sync main to gitcode (mirror)
|
||
runs-on: ubuntu-latest
|
||
needs: [go, race, cross, gui, csrc, docs]
|
||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||
timeout-minutes: 10
|
||
steps:
|
||
- id: tok
|
||
name: 检查 gitcode 凭据
|
||
run: |
|
||
if [ -n "${{ secrets.GITCODE_TOKEN }}" ]; then
|
||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||
echo " 未配置 GITCODE_TOKEN —— 跳过 gitcode main 同步"
|
||
fi
|
||
- uses: actions/checkout@v7
|
||
if: steps.tok.outputs.ok == 'true'
|
||
with:
|
||
# 必须取完整历史:镜像可能在任意旧提交上,快进需要共同祖先。
|
||
fetch-depth: 0
|
||
# ★ 改名(HomeAgent → HologramAgent)后的必然过渡态:gitcode 上的镜像仓
|
||
# 是**另一个账号下的独立仓库**,改 GitHub 仓名不会自动建出它。
|
||
# 若不在这里显式探测,缺少目标仓时只会得到 git 的原始报错
|
||
# ("repository not found" 混在 push 输出里),看不出是「仓没建」。
|
||
# 语义:token 未配置 = 可选项跳过(上面那步);token 有但仓不存在 = 硬失败。
|
||
#
|
||
# ★ 有界重试(2026-10-21 dfc4e16 的教训):首次跑这一步时 gitcode 网络抖动,
|
||
# 一次 ls-remote 判失败 ⇒ job 红;重跑立刻全绿。现在只对**网络层**失败
|
||
# 重试(3 次,退避 5s/10s),而「仓不存在」与「凭据问题」**立即失败不重试**
|
||
# —— 后两者重试只会把真问题拖长、并把它埋在噪声里。
|
||
#
|
||
# 实现落在 .github/scripts/check-gitcode-repo.sh,与 release.yml 的
|
||
# sync-gitcode **共用同一个文件**(同口径靠同一份代码保证,不靠人抄两遍)。
|
||
- name: 前置检查 gitcode 目标仓是否存在
|
||
if: steps.tok.outputs.ok == 'true'
|
||
env:
|
||
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||
# 显式传环境变量(而不是 shell 局部变量):脚本的默认值与这里
|
||
# 相同,但写在 env 里才是**真的传下去**,改仓名时不用改两处逻辑。
|
||
GC_REPO: JianFeeeee/HologramAgent
|
||
run: |
|
||
set -euo pipefail
|
||
bash .github/scripts/check-gitcode-repo.sh
|
||
- name: 推 main 到 gitcode(快进)
|
||
if: steps.tok.outputs.ok == 'true'
|
||
env:
|
||
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||
run: |
|
||
set -euo pipefail
|
||
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
|
||
git push "${GC_URL}/JianFeeeee/HologramAgent.git" \
|
||
"refs/heads/main:refs/heads/main"
|
||
echo " gitcode/main 已快进到 ${GITHUB_SHA}"
|