姚章浩
|
0974d8dfa2
|
feat(version-history): 「版本记录」页 —— 追踪远端 main 全部提交(只读 + 管理员手动同步)
build / build (push) Successful in 57s
按已确认的契约实现(口径见 docs/VERSION_HISTORY.md):
- **版本号口径(只给有证据的)**:注释 tag + tag 指向该提交 + 该提交内 CHANGELOG 顶部三者一致 ⇒ 正式版本;
tag 之后的提交按 `<主>.<次>.<自该 tag 的提交数>` 显示开发号(与 scripts/version.py **同规则**);
证据不足 ⇒ 「历史提交 · 短SHA」+ 原因码,**不补造版本号**。完整 SHA 是唯一身份、版本号不是唯一键
(tag 之后两条并行分支各自的第 1 个提交都算 0.1.1 ⇒ 两条都保留,用短 SHA 区分)。
- **迁移 000046**:version_history_commits(提交记录,主键 repo_key+commit_sha)+ version_history_sync_runs
(同步状态)。快照 = 一次同步:抓完并与 X-Total-Count 对账通过才切换;失败不切换快照、不清空历史、
last_success_at 不动(页面显示"上次同步失败(原因)· 仍展示上次成功数据");「同步成功」≠「历史完整」。
- **只读客户端(匿名,不使用任何凭据)**:白名单 origin + 固定仓库、拒绝任意 URL 与跨主机重定向、
响应上限 5MB / 超时 15s、仅 5xx/429 退避重试(最多 3 次);逐页抓到短页为止;每次全量扫 tag
⇒ 旧提交后补的 tag 会更新既有记录(不新增第二行)。
- **接口**(三个都登记进 api/openapi.yaml):GET /api/v1/build-info(viewer)、
GET /api/v1/version-history(viewer;游标绑定快照,过期返回 409 cursor_expired;不返回伪造 total)、
POST /api/v1/version-history/sync(admin;并发同步复用同一作业,靠 IdempotencyKey)。
- **「当前运行」只认精确匹配**:产物注入的完整 SHA 命中某条记录才标该条;开发构建显式标注"开发构建";
没有可用标识 ⇒ "当前运行版本未知";**不拿最新 main / 最新 tag 顶替**,CI 成功不等于已部署。
- **前端**:一级菜单第 5 项(版本记录)+ 时间线页面(全部/正式/开发/历史四段筛选、正文展开、
加载更多、760px 单列无横向溢出、提交正文与变更记录按纯文本渲染);无打 tag / 发布 / 部署入口。
新增配置(留空即未接线,页面降级):VERSION_HISTORY_GITEA_BASE_URL / _REPO / _ALLOWED_ORIGINS /
_HTTP_TIMEOUT / _MAX_PAGES。
验证:`make verify` 15/15 success(tests-go 578 通过 / 0 失败;tests-web 209 通过 / 0 失败);
新增 Go 用例 30(版本口径与图遍历 12、客户端守卫 6、同步编排 5、接口 7);前端新增 18(组件 10 + 客户端 8);
真实仓库匿名快照拉取(`VERSION_HISTORY_LIVE_BASE_URL=… go test -run TestLiveSyncAgainstRealRepository`):
抓到 113 条与 X-Total-Count 一致、v0.1.0 是注释 tag 且目标与 `git rev-parse v0.1.0^{commit}` 一致、
开发号逐个等于 `git rev-list --count v0.1.0..<sha>`(正式 1 / 开发 22 / 历史 90)。
|
2026-10-13 19:40:19 +08:00 |
|
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 |
|