Commit Graph

5 Commits

Author SHA1 Message Date
t 6de5d52363 feat(version): 开发号随提交自动递增 + 首个正式版本 0.1.0
build / build (push) Successful in 51s
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 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
姚章浩 7a9d51cc70 fix(version): 正式构建核证据 + 预检分"建议/就绪" + CI 就绪按必需 context
按要求补三项(本轮不推、不打真实 tag、不部署;全部用临时仓库与假对象验):

1) 正式构建身份不能只认 --release:
   - 新增 release_evidence():核①完整 40 位 SHA ②**指向该提交的注释 tag** v<版本号>
     ③该提交里 CHANGELOG 顶部版本一致。缺任一条 ⇒ rc=2,身份退回 development 并把
     release_blocked 的原因写进 JSON(给机器读,也给人看)。
   - version.py --self-test:六条负例(有版本无 tag / unknown SHA / 短 SHA / tag 指错提交 /
     轻量 tag / 版本不一致)+ 证据齐正例,**tag 全部在 mktemp 临时仓库里创建**。
   - Go 侧新增 TestReleaseBuildRequiresEvidence:release 构建必须 CommitSHA 完整且
     v<ReleaseVersion> 是**注释 tag** 并指向该提交(独立于 Python 的二次复核)。

2) 发布预检区分"准备建议"与"发布就绪":
   - 新增纯函数 readiness(facts) → (是否就绪, 阻塞项, 建议);输出分两段。
   - **就绪要求候选版本已写进目标提交**(version_in_commit:查 git show <sha>:CHANGELOG.md,
     工作区写了没提交不算);首发按**显式指定版本**处理,**不把"记录里有这个版本"当已发布**
     (已发布的证据是 tag,不是记录条目)。
   - 预检**不执行、也不在输出里推荐自动发布路径**(正式发版由人按 docs/RELEASE_RUNBOOK.md)。

3) CI 就绪判据:必需 context 集合来自 .gitea/required-contexts.txt(缺失/为空 ⇒ 没有策略 ⇒
   阻断,不用观察到的状态顶替);**全部 success 才通过**,失败/缺项/pending/查询异常一律阻断。
   Release 查询加 release_state():**只有 HTTP 404 才算不存在**,403/5xx/超时算查询异常 ⇒ 阻断。

验证:version.py --self-test 绿;release_preflight --self-test 绿(就绪判定 19 个分支 +
临时仓库验证 git show 语义 + 五个 HTTP 码语义);go test 三态(未注入 SKIP / 开发 PASS /
自称正式 RED);反向夹具 83/0(含临时仓库 go test 六例、预检"记录未进提交"两态);
make verify 15/15 success。
2026-10-13 15:26:49 +08:00
姚章浩 05144309c3 feat(version): 构建身份(release_version/build_kind/display_version),无基线也放行
解决"开发构建沿用已发布版本号,分不清开发版与正式版":

- 新增 `scripts/version.py --identity`:release_version(无基线即空)/ build_kind /
  commit_sha(完整 40 位)/ display_version / built_at。显示规则:发布 `X.Y.Z`、
  开发 `X.Y.Z-dev+g<短SHA>`、无基线 `dev+g<短SHA>`、什么都拿不到 `dev`。
- `build_kind=release` 只认**显式发布上下文**(`BUILD_KIND=release` / `--release`);
  声明发布构建却没有已发布版本 ⇒ 拒绝(rc=2)。普通 main 构建永远是 development,
  **不许只显示上一正式版本号、不许自称正式版**。
- Go 侧 `internal/version` 加 ReleaseVersion/BuildKind/DisplayVersion()/IsRelease()/Info(),
  原 Number/CommitSHA/BuildTime 语义不变(向后兼容);契约测试改成:发布构建必须等于
  CHANGELOG 顶部、开发构建必须是 dev 形状;`make verify` 的 contracts 项新增
  **负例**「自称正式版 ⇒ 必须红」(只有正例的契约会被静默掏空)。
- 构建接线:Makefile/Dockerfile/ci_checks `--internal-build` 统一走 identity;
  Makefile 的 `make version` 打印身份 JSON;Dockerfile 新增 RELEASE_VERSION/BUILD_KIND。
- `tests-go` 的"按设计跳过"声明补上 `TestDevBuildDoesNotClaimRelease`(它的强制点在 contracts)。

验证:go test 三态实测(未注入 SKIP、开发身份 PASS、自称正式版 FAIL);
--internal-build 产出并打印 dev+g<sha>;--internal-identity 契约项 rc=0;
make verify 15/15 success;反向夹具 80/0。
2026-10-13 14:59:36 +08:00
姚章浩 c04abd6530 style(go): 清掉存量 gofmt(6 个文件的字段对齐与文件末尾换行)
接入格式门禁前先量了一次存量:gofmt -l 在 295 个被跟踪的 .go 文件里报 6 个,
全是字段对齐与缺文件末尾换行这类机械问题。先一次性清干净,门禁接上后 Go 侧一条都不欠。

为什么独立一个提交:格式清理混进功能改动会让 diff 无法 review(正本要求存量清理独立提交)。
验证:gofmt -l 对被跟踪的 .go 文件输出为空;go vet 与 go test 全过。
2026-10-12 00:10:31 +08:00
姚章浩 5f3a71cade feat(version): 版本号唯一来源 = CHANGELOG.md,并要求写清改了什么与怎么验收
build / build (push) Successful in 26s
背景:本仓此前没有任何版本/构建元信息 —— 没有 ldflags 注入、也没有产物可读的
版本号,"线上跑的是哪个提交"答不出来;"这次改了什么、怎么验收"也没有载体。

落地:
- CHANGELOG.md:**版本号的唯一来源**(正本 §6.1:版本号只准有一处)。刻意不放
  VERSION 文件、也不靠 git tag —— CI 是 `git fetch --depth 50`,取不到 tag,
  `git describe` 会静默退化。每个已发布版本必须有两节:### 改了什么 与 ### 怎么验收。
- scripts/version.py:从 CHANGELOG 顶部解析版本号;解析不到即非 0 退出(构建据此失败)。
- scripts/check_changelog.py:结构自检(两节齐、带日期、版本号不重复且严格递减、
  小节不为空),带 --self-test 反向自检。
- internal/version:三个构建期注入变量 + 契约测试(注入的号必须等于 CHANGELOG 顶部)。
- Makefile:build 带 -ldflags 注入;verify 增加 changelog-check 与 version-contract。
- Dockerfile:ARG VERSION/COMMIT_SHA/BUILD_TIME + ldflags(未透传时落 dev —— 故意显眼,
  好过看起来正常的假版本号)。
- CI:新增「版本与变更记录契约」步,并显式断言契约测试真的 PASS(被跳过的断言等于没有断言)。
- docs/提交规范.md(ci_gate.py 的报错文案一直在指它)、docs/agents/traps.md(7 条实测陷阱)。

验证(都真跑过):
- make version → 0.1.0;make changelog-check → 合规
- make version-contract → PASS;注入 9.9.9(CHANGELOG 里没有)→ FAIL(反向自检)
- 删掉真实 CHANGELOG 的「怎么验收」小节 → 报红(对真实文件做,不只是夹具)
- go build ./... 与 go vet ./internal/version/... 通过

未做(会与在审 PR #2 冲突,待其合并后在 main 上补):
- GET /version 端点(internal/httpapi/server.go)与 cmd/api 的 -version 开关
- compose.yaml 透传 build args(当前由 compose 构建的镜像会报 dev)

⚠️ 未跑 make verify 全量(含 go test ./... 与前端),CI 是本次的测试证据。
2026-10-10 18:13:24 +08:00