Files
knowledge-governance/docs/RELEASE_RUNBOOK.md
T
t 6de5d52363
build / build (push) Successful in 51s
feat(version): 开发号随提交自动递增 + 首个正式版本 0.1.0
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 tag 仍由人批准):

- **开发号自动递增**:display_version(development) = `<正式版本的主.次>.<自 v<正式版本> 以来的提交数>`
  (正式 0.1.0 之后第 1 个提交 0.1.1、第 12 个 0.1.12;恰在发布提交上 = 正式号)。
  它**不写进 CHANGELOG**(发布记录只在发版时动),由 git 算出来 ⇒ 每个提交都有自己的号、数字只增。
- **浅克隆不猜号**:`--depth` 会把条数算少 ⇒ 浅仓库里拒绝算号,退回 `<版本>-dev+g<短SHA>`;
  检出步骤在仓库有 tag 时 `--unshallow` 补全历史。
- 接线三处齐全:`version.py --identity` → 门禁 `-X …CommitsSinceRelease=<n>`;Makefile 的
  `COMMITS_SINCE_RELEASE`;Dockerfile 同名 build-arg;Go 侧新增 `CommitsSinceRelease` 与
  DisplayVersion() 新规则;契约测试**交叉核对**"注入的提交数 == git 算出的"(防注入错号)。
- **首个正式版本 0.1.0**:CHANGELOG 的 `[Unreleased]` 提成 `## [0.1.0] - 2026-10-13`;
  头部补"版本怎么走"两行;RELEASE_RUNBOOK 补递增规则与"下一次发布把次版本加一"。
- 发版预检新增:打印**当前开发号**,并挡住"候选正式版本 <= 当前开发号"(会把开发号当正式号;建议次版本加一)。

验证:version.py --self-test(含 tag 后第 2 个提交 = 0.2.2、恰在 tag 上 = 0.2.0、无 tag 不猜号);
release_preflight --self-test(21 分支,含开发号两条);go test 本地三态 + 临时仓库 tag+2 提交实测
(Python 与 Go 都得出 0.2.2);反向夹具 85/0;make verify 15/15 success。
2026-10-13 16:45:56 +08:00

6.0 KiB
Raw Blame History

发布说明(正式版本)

这份文档是破坏性变化与数据迁移步骤的落脚点:CHANGELOG.md 里以 **破坏性** 开头的条目 必须链接到本文(或本文的某个小节)。发布记录本身仍在 CHANGELOG.md —— 本文不维护第二份变更列表。

本仓 CI 只做 CI:不打 tag、不发 Release、不部署。发布与部署都是独立的人工审批动作。

一、正式发布(由人批准后执行)

  1. 确认基线:git rev-parse HEAD 拿到完整 40 位 SHA,工作区干净(git status --porcelain 为空)。

  2. 写记录:把这次要发的东西从 [Unreleased] 提到一个正式版本条目 ## [x.y.z] - YYYY-MM-DD(四类小节,无空栏目;破坏性变化写在 ### Changed 里并链接本文)。 版本号只增不减(回退走部署层,不改这个文件);每个项目只有本文件这一个版本来源。

  3. 跑预检(只读):python3 scripts/release_preflight.py --version X.Y.Z —— 输出两段:准备建议(还差什么)与 发布就绪判定(是否就绪)。 ⚠️ 预检不执行、也不推荐自动发布路径:自动发布路径已禁用 —— --execute / --approve-* 只保留参数兼容(不报 unknown argument),传了会被明确拒绝(rc=2,拒绝发生在收集事实之前 ⇒ 零写操作、零 API 调用)。正式发版动作由人按本文执行。 就绪 = 证据齐(缺一条都不算):

    判据 说明
    候选版本已是目标提交里的顶部版本 查 git show <sha>:CHANGELOG.md;只在工作区写了、没提交 ⇒ 不就绪;只在历史里出现过(顶部是别的版本)⇒ 也不就绪;预检会把该提交的顶部版本打出来
    完整 40 位 SHA 指向准确提交 目标提交 == HEAD,或 --sha 逐字符一致
    记录检查通过 结构与 tag 一致性(真查远端)
    必需 context 集合全部 success 集合来自 .gitea/required-contexts.txt;缺失/为空 ⇒ 没有策略 ⇒ 阻断;失败 / 缺项 / pending / 查询异常一律阻断
    远端 tag 已核对且未被占用 查不到远端 ⇒ 阻断
    Release 不存在 只有 HTTP 404 才算「不存在」;403/5xx/超时 ⇒ 查询异常 ⇒ 阻断
    首发版本号 还没有已发布版本 ⇒ 必须显式指定;不把「记录里有这个版本」当作已发布

    自查(不依赖网络、不打真 tag):python3 scripts/release_preflight.py --self-test、 python3 scripts/version.py --self-test。

  4. 由人执行(预检不做这件事):创建注释 tag vX.Y.Z(指向验收过的那个提交,完整 40 位 SHA) 并建关联 Release,正文取自 CHANGELOG.md。⚠️ release_preflight.py --execute 已被禁用 (见第 3 步),不要试图用它自动发版。

  5. 读回:git ls-remote --tags origin 里该 tag 的 SHA == 本地 git rev-parse vX.Y.Z^{commit}; Release 的 tag_name 正确。已发布的 tag 不移动、不覆盖。

⚠️ 三个状态分开说:CI 成功 ≠ 正式发版 ≠ 已部署。

二、构建身份怎么看

python3 scripts/version.py --identity     # JSON:release_version / build_kind / commit_sha / display_version / built_at
情形 build_kind release_version display_version
普通开发构建(有正式基线,tag 也在) development 顶部已发布版本 <主.次>.<自该 tag 的提交数>,如 0.1.12
开发构建恰好在发布提交上(提交数 = 0) development 同左 就是那个正式版本号
普通开发构建(还没有正式基线 / 取不到 tag) development null dev+g<短SHA>
发布构建(显式发布上下文 + 证据齐) release 顶部已发布版本 X.Y.Z(另带完整 SHA)

开发版本自动递增规则:<正式版本的主.次>.<自 v<正式版本> 以来的提交数> —— 第 1 个提交 0.1.1、第 12 个 0.1.12;数字只增、每次提交必不同,且不写进 CHANGELOG (发布记录只在发版时动)。打新 tag 后从新 tag 重新起算 ⇒ 所以下一次正式发布请把"次版本"加一 (0.1.x 之后发 0.2.0),这样正式号永远严格大于此前出现过的所有开发号,不会撞号。 证据字段:commits_since_release(自 tag 以来的提交数,无 tag 为 null)。

  • build_kind=release 只能来自显式的发布上下文(BUILD_KIND=release),并且要核证据: 完整 40 位 SHA + 指向该提交的注释 tag v<版本号> + 该提交里 CHANGELOG 顶部版本一致; 缺任何一条 ⇒ 拒绝标为正式版(--identity --release 以 rc=2 退出、身份退回 development 并列出缺什么)。普通 main CI 永远是 development,不得因为"CHANGELOG 顶部有版本号"就自称正式版。
  • 二进制里的注入字段(internal/version)与上表一一对应;契约测试断言: 注入 == CHANGELOG 顶部(发布构建)、开发构建必须是 dev 形状(不许声称正式版),且跳过不算通过。

三、破坏性变化 / 数据迁移怎么写

在 CHANGELOG.md 的 ### Changed 里:

- **破坏性**:接口 `/api/old` 下线,升级步骤见 [迁移步骤](docs/RELEASE_RUNBOOK.md#三破坏性变化--数据迁移怎么写)

要求:必须以 **破坏性** 开头(便于发版前扫出来)、必须带本文链接;仓内相对链接会被 scripts/check_changelog.py 对账(文件不存在 ⇒ 红)。迁移步骤要写清「先做什么、怎么验证、怎么回退」。

四、回滚

回滚不改 CHANGELOG.md、不动 tag:走部署层(恢复上一个已验收的镜像/版本 + 读回证据)。 本仓部署流程在 docs/OPERATIONS_RUNBOOK.md 与各环境的部署脚本里;部署是独立审批, 并要记录"实际部署的版本 + 完整 SHA"。