Files
eng-standards-main/04-模板评估与修正.md
T
姚章浩 5b65ed9579 docs: 正本与 Runbook/接入文档口径同步(测试语义、CI-CD 分离、门禁唯一入口、必需检查清单)
按草案逐条落到正本,并把三处已知错误的口径改回:

① 测试语义:删掉"单测跑的是构建产物,不是源码"这条被过度推广的说法。
   只有"跑产物"的套件(端到端 / 契约 / 产物自检)才必须排在构建之后;
   go test 自己编译测试程序、pytest 直读源码、vitest 默认也跑源码。
   写错这条的代价是给每个套件加假依赖、拖慢门禁并制造假故障。T8 同步改。
② CI 与 CD 分离:触发表删掉"推默认分支 → 自动部署测试环境",作业划分表删掉 deploy:test 行,
   §4.6 标注为"项目自己的 CD、不属于通用 CI",速查表同步。
③ 门禁入口:统一为 make verify(名字可不同,但 AGENTS/Runbook/CI 必须指同一个),
   §3.1、§8 清单、§9 速查表一致。
④ 新增 §4.8「必需检查清单与结果口径」:10 项必需检查的判据表 + 三条结果口径
   (失败即停但汇总照做、not_run/skipped 不算过、汇总故障不能报绿),
   以及预览/通知作为旁路的规则。
⑤ 接入文档:修掉"直推也拦得住"这句错口径,并写明接入器不再自动合并、
   --merge-pr 会先核对状态;试点记录里那一步标注为"当时的自动合并行为已改"。
⑥ 03/04/05 里的同源表述一并同步(生成物 jobs 与假成功占位的判据)。

验证:本批 18 处精确替换在脚本里逐处断言"恰好命中 1 次"(DRY-RUN 先过一遍再应用);
替换后全仓 grep 复查,旧口径只在"说明它已被改掉"的上下文里出现。
2026-10-12 00:12:06 +08:00

11 KiB
Raw Blame History

模板评估与修正(附件 eng-standards-main.zip)

结论先说:原稿的"地基"(四条铁律、反向自检、"为什么"写在正文、门禁串行失败即停)全部保留; 但它整体是 GitLab / glab 口径,而本环境是 Gitea 1.26.2 + act_runner + 自建 ci-bridge + 阿里云双机。 逐条评估结果如下。每条的"证据"都是本次在本机/实例上实测到的,不是推断。

一、结论

分类 数量 处理
A. 平台错配(GitLab 语义在本环境不存在) 6 全部改写为 Gitea 口径
B. 与实测事实矛盾(原稿断言不成立) 3 修正断言,写清成立条件
C. 模板内部自相矛盾 / 数值不可信 3 改掉矛盾点
D. 具体取值是他人环境的 3 全表换成"本环境事实"(README.md §1)
E. 覆盖缺口(原稿根本没约束) 1 新增铁律(同一份构建产物,规范 §5.1)

A. 平台错配(6 处)

A1. 触发体系:workflow: rules / $CI_* 全部不存在

  • 原稿:.gitlab-ci.yml 的 workflow.rules 用 $CI_PIPELINE_SOURCE == "web" 与 $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH 两条件。
  • 实测:本实例是 Gitea Actions,.gitea/workflows/*.yml,触发在 on:;可用变量是 GITHUB_*(GITHUB_REF_NAME、GITHUB_REPOSITORY、GITHUB_SERVER_URL)。
  • 修正:见规范 §4.1,三类入口写成 on.push.branches 与 on.workflow_dispatch。原稿那条"刻意不接 tag"的判断保留,理由改写(见 A2)。

A2. 手动作业语义:when: manual 的"阻塞型"论证是 GitLab 专有

  • 原稿:手动作业没有 allow_failure: true 时流水线永远到不了 success(停在 blocked),取消会把已成功作业一起标 canceled。
  • 实测:Gitea 里 if: 不成立的作业是 skipped,不会把运行挂死;生产发布的正确形态是独立的 workflow_dispatch 运行。
  • 修正:保留原稿的真问题("取消 ≠ 回滚"、"不要重写会抹掉证据"),但改挂到 Gitea 的真实风险上:concurrency.cancel-in-progress 会取消正在进行的部署。生产 workflow 必须 cancel-in-progress: false。

A3. resource_group 在 Gitea 1.26.2 不存在

  • 原稿:同一环境的部署用 resource_group: <env> 防并发。
  • 实测:Gitea 没有 resource_group;concurrency: 只对同 workflow 同 group 生效,跨 workflow / 人手手动执行挡不住。
  • 修正:互斥下移到目标机(flock -n,规范 §5.4)。这是"可靠性只在这一层"的取舍,必须写明。

A4. 变量作用域:Gitea 没有 environment scope,也没有 File 类型 secret,勾选坑不同

  • 原稿:GitLab CI/CD Variables 用 environment scope 区分环境;有 "Expand variable reference" / "Masked" / "Protected" 三个勾选坑;私钥用 File 类型(因为多行勾不了 Masked)。
  • 实测:Gitea 仓库变量/密钥没有环境作用域;私钥只能整体存 secret(无 File 类型);三个勾选坑不存在。
  • 修正:环境区分改用命名后缀(DEPLOY_TEST_* / DEPLOY_PROD_*);私钥由运行时脚本落盘 0600(规范 §7.1 / §7.3)。 另有接口形状要写对(实测):变量 POST/PUT …/actions/variables/{name} body {"value":…};密钥只有 PUT …/actions/secrets/{name} body {"data":…};集合级 POST 返回 405 空 body(像权限问题,实际不是)。

A5. CLI:glab → giteactl / gitea_api.py

  • 原稿:§6 整章讲 glab,含 projects/<ns>%2F<repo> 显式路径坑(细粒度只读 token 下 :id 占位符会 403)。
  • 实测:本机没有 glab;有 ~/.local/bin/giteactl(runs/show/logs/watch/rerun)、~/.hermes/scripts/gitea_api.py、giteacli(本机 token 缺 read:user,whoami/login 会失败但仓库功能可用)。
  • 修正:§9 速查表与 §02 Runbook 换成 giteactl/gitea_api.py 的真实命令。原稿"读日志用显式路径、不用交互式 TUI、trace 要清洗"的经验保留,但换成 Gitea 的日志接口地址。

A6. GIT_DEPTH: 1 / after_script + $CI_JOB_STATUS 通知 / tags: [docker]

  • 原稿:CI 变量 GIT_DEPTH: 1;通知用 after_script 拿 $CI_JOB_STATUS;Runner 标签 docker。
  • 实测:本环境是自 clone 模型(git fetch --depth 50);通知走 .gitea/scripts/ci_report.py + if: always() 步骤 + 主机侧 bridge;Runner 标签是 ubuntu-latest/24.04/22.04。
  • 修正:全部替换。原稿"通知失败不能让作业失败"、"可能的话两条通知链路分开"的判断保留。

B. 与实测事实矛盾(3 处)

B1. "不用任何第三方 action" —— 本环境不成立

  • 原稿:文件头写"不用任何第三方 action(作业容器不联网拉 action)",并把它当铁律。
  • 实测:aiball/software-pc 的 .gitea/workflows/validate.yml 用了 actions/checkout@v4 + actions/setup-node@v4,run#1 = success(GET /repos/aiball/software-pc/actions/runs 显示 completed success)。而 Weknora 三仓的 runner 上工作流刻意自 clone。
  • 修正:规则改成按 Runner 配置区分(规范 §4.3):默认不用 uses:;例外要在文件头写明该 runner 支持取 action。并给出一条判据:"时红时绿且报拉取失败"先怀疑 uses:。

B2. .gitignore 模板与正本条款自相矛盾

  • 原稿:正本 §1.3 明说"处理办法不是加 ! 例外(例外越加越多,规则本身就没人看得懂了)";但 templates/gitignore 里就给了 !src/secrets/ 和 !src/secrets/**。
  • 实测:原稿自己的 spec-check.mjs 的 ARTIFACT_PREFIX 正则包含 secrets/ —— 于是"按模板加了例外、又真把 src/secrets/ 入库"的工程会被 tracked-artifacts 判红。
  • 修正:模板去掉 ! 例外,改为一句话:"别在源码路径里用 dist/data/secrets 这些名字"(见 templates/gitignore)。

B3. spec-check.mjs 的 AGENTS_MAX_BYTES = 32768 与正本数字不一致

  • 原稿:正本 §2.4 写"实测 65983 字节时尾部 739 字节被静默截断";而守门判据硬编码 AGENTS_MAX_BYTES = 32768。
  • 矛盾点:正本自己立了规矩"文档里不许出现会漂移的数字,要么由脚本打印要么写成断言"(§4.3),却在这里放了一个来源不明的常量。
  • 修正:本次不引入该脚本(任务边界是"只制定规范"),但在 03-待配置事项.md 第 16 项写明:改写 Gitea 版守门时,上限值必须由一次真实测量得出并写成断言,不许手抄。

C. 模板内部问题(3 处)

C1. templates/verify.mjs 写死跨机路径

  • 原稿:门禁第 5 步 cmd: [process.execPath, '__STANDARDS_DIR__/scripts/spec-check.mjs', ...] —— 一个绝对路径,在 CI 容器里根本不存在(原稿自己的 validate:repo 注释也承认了这一点)。
  • 修正:门禁入口只跑本仓的东西;规范守门要么随仓携带(.gitea/scripts/),要么在 CI 里先 git clone 规范仓库到固定路径。见 templates/gitea-build.yml 的 validate 作业注释。

C2. spec-check.mjs 两条判据过窄/无对象

  • ci-cache-node-modules:只匹配 - node_modules/ 这一种写法;本环境的缓存是主机挂载目录(/opt/citools-cache),判据完全够不着。
  • glab-placeholder:在 Gitea 环境里没有 glab,判据无对象(永远是 ok)。
  • 修正:说明见 03-待配置事项.md 第 16 项。同时保留原稿两条好经验:判据要认"这件事"而不是"那种写法"(原稿自己在 v1.0.4 里踩过)、并且"报错文案里也不能出现针的字面量"(会被下一次扫描命中)。

C3. check_env.sh 的必需键是按"网关 + Cookie"项目定的

  • 原稿:REQUIRED="ENV_NAME APP_DIR DOMAIN COOKIE_NAME",并断言"两个环境的父域 + Cookie 名不能同时相同"。
  • 实测:该不变量属于原稿样板项目(带 nginx + Cookie 的网关)。本环境的项目是 Go 服务(DobotAI / knowledge-governance / adapter),没有这一对不变量;DobotAI 的环境参数在生产机 /opt/dobotai/config/dobotai.env(不入库),也不是 deploy/env/*.env。
  • 修正:本规范不照搬该脚本。保留它的形状("仓库层面的不变量放在 CI 校验,在传任何字节之前失败"),但要按项目定键(规范 §7.2)。

D. 具体取值是他人环境的(3 处 → 全表替换)

原稿取值 本环境实际
https://gitlab.artificialheart.cn/liuzhufu/...、默认组 liuzhufu http://192.168.50.223:3000(https://gitea.zheromiku.top);组 Weknora / aiball
Runner 标签 docker;镜像 node:24-alpine 标签 ubuntu-latest/24.04/22.04 → docker://docker.gitea.com/runner-images:*
APP_DIR=/volume1/docker/...(群晖 NAS)、域名 *.artificialheart.cn / *.dobotoa.cn 阿里云 ECS:测试 120.76.217.51、生产 120.76.218.137,路径 /opt/dobotai/...

以上全部换成了 README.md §1 的唯一取值表,并给了取数命令(避免手抄漂移)。


E. 覆盖缺口(1 处 → 新增铁律)

E1. 「生产使用测试验收过的同一份构建产物」原稿没有约束

  • 原稿:§5.5 讲产物按生命周期拆包、§5.7 讲目标机三层布局与回滚,但没有要求"测试与生产部署同一份产物"。
  • 实测:本环境现状恰恰相反 —— DobotAI 的既有流程是在目标机上各自 build(生产机 clone 源码 + docker build),测试与生产不是同一份二进制。
  • 修正:作为本次规范的第一优先级新增(规范 §5.1):构建只做一次 → 产出 image_id + tar_sha256 写进发布清单 → 测试与生产都 docker load 同一份 tar 并各自断言 image_id 相同。并给出完整的证据链表(§5.1)与"迁移不属于镜像、先迁移后切镜像"的例外。

F. 保留的(原稿最有价值的部分,不要因为"平台换了"就丢掉)

  1. §0 四条地基:结论只来自可复现命令 / 每个校验都要反向自检 / 密钥不进命令行 / 提交前跑门禁。
  2. §3.2 八条通用铁律:只有"跑产物"的套件要排在构建之后(不是"所有单测都依赖预构建产物")、干净 clone、反向自检三问(会红吗 / 会假绿吗 / 真的作用在目标上吗)、断言要有唯一命中点、夹具要打扫、先确认无遗留进程。
  3. §2.2 正文写"为什么" 的四种要素(现象/根因/取舍/证据)与示例形状。
  4. §2.3 heredoc 禁忌(反引号 $() 会被 shell 先执行,且退出码仍是 0)。
  5. §1.3 .gitignore 是契约 与两条反查命令。
  6. §5.4 密钥只走 stdin 写文件、chmod 600、去 CRLF。
  7. §5.7 目标机三层布局 与"停容器/换目录必须在同一个脚本里、顺序不许挪"。
  8. §5.6 不缓存 node_modules。
  9. §2.4 陷阱库拆两份(指令文件放索引、旁边放全文,因为指令有字节预算)。
  10. §11 规范自己怎么被管理(单一来源、投影、可检验、有版本)—— 见本目录的 README.md 与 03-待配置事项.md 第 13 项。