Commit Graph

77 Commits

Author SHA1 Message Date
姚章浩 4d6ecda310 feat(libraryctl): 源对象存储端点可按宿主机地址覆盖
build / build (push) Successful in 1m13s
容器 env 里的 MINIO_ENDPOINT 往往是服务名(langfuse-minio:9000),封存跑在宿主机上
解析不到,真实封存报 read library resource 并整体失败。
新增 -source-s3-endpoint:只换访问入口,桶与对象键不变;缺 scheme 直接拒绝;
status 会打印实际使用的 endpoint。

试过:make verify 15 项全绿;新增覆盖用例(生效 + 拒绝无 scheme)。
2026-10-29 06:09:56 +08:00
姚章浩 11c52ad81b fix(libraryctl): 封存窗口接上实例级入口闸门与后台静默
build / build (push) Failing after 1m2s
库级窗口永远拿不到在途排空证据:被代理的 WeKnora 收到 SIGTERM 就 exit(1),
写者退出码不能当作请求已排空。capture/recover 改由 librarymaintenance.Manager
装配入口闸门、后台静默检查、目标指纹与入口探针(与发布链路同一套实现),
排空证据来自闸门 drain 记录;缺 LIBRARY_TARGET_* 时在开窗口前拒绝并点名。

维护接线解析收敛到 internal/librarymaintenance/env.go,agent 与 worker 讲同一组
变量名;agent 启动如实打印接线状态;安装脚本强制 0600 agent.env。

试过:make verify 15 项全绿;新增 7 个用例(接线解析/身份绑定/缺闸门拒绝)。
2026-10-29 05:48:38 +08:00
姚章浩 ee7688f24e docs(matrix): Wiki 撤销检查实际 13 项,改正计数
build / build (push) Successful in 1m13s
2026-10-29 05:18:42 +08:00
姚章浩 9d0fe33c13 docs(ledger): 补记 0.1.162–0.1.167 与验收代码 SHA
build / build (push) Successful in 1m13s
0.1.166(1faf513)是本轮验收代码 SHA;f82eb07 起的文档提交不改代码。
0.1.165 因后续提交被 CI 取消,按事实记为 cancelled,不改写成成功。
2026-10-29 05:15:53 +08:00
姚章浩 f82eb0735f docs(acceptance): 矩阵按四段重排,补封存链路缺口与 8088 部署方案
build / build (push) Successful in 1m14s
矩阵不再在顶部叠报告:已完成 / 缺代码 / 已实现未跑 / 需授权四段分开,
把本轮 Wiki 撤销真实验收(12 项检查)、贡献级差异、维护面板写进对应行,
并如实记下封存链路缺口(CLI 无入口排空证明、API 封存未接线)、
死信无解除契约、Neo4j 场景与排空探针未跑。

新增正式 8088 待验收部署方案:迁移 067–072、loopback 配置差异、
缺配即拒绝的 fail-closed 规则、回滚与账号数据保留。只准备,未执行。
2026-10-29 05:13:15 +08:00
姚章浩 def6022048 docs(ledger): 补记 0.1.137/0.1.138 并把进度文件更新到最终真实版本
build / build (push) Successful in 1m14s
2026-10-28 20:19:10 +08:00
姚章浩 e7ba2057ac docs(matrix): V03 标注架构阻塞、P02 真实计数、R01 恢复已验证
build / build (push) Successful in 1m12s
2026-10-28 20:06:07 +08:00
姚章浩 f722ea8727 docs(ledger): 补记 0.1.135 cancelled 与 0.1.136 success
build / build (push) Successful in 1m11s
2026-10-28 19:34:27 +08:00
姚章浩 e47e6d825d docs(matrix): 追加入口级 8 场景真实验收对照
build / build (push) Successful in 1m14s
为什么:矩阵要与固定 SHA 的证据文件对应。
2026-10-28 19:31:17 +08:00
姚章浩 7a2188a0ee docs(release): 入口级 8 场景真实验收证据与台账
build / build (push) Has been cancelled
为什么:证据必须对应固定 SHA;旧证据与报告冲突的部分保留但不再引用。
2026-10-28 19:31:09 +08:00
姚章浩 ca51cbe202 docs(release): 台账补记 0.1.131–0.1.132 与候选验收报告
build / build (push) Successful in 1m17s
为什么:版本→SHA→CI→是否部署要可审计,候选 E2E 证据要指向固定路径。
2026-10-28 17:58:42 +08:00
姚章浩 8a68ea2da2 docs(release): 台账补记 0.1.129–0.1.130
build / build (push) Successful in 1m21s
为什么:候选 SHA、CI 结论与真实服务验收事实要落台账。
2026-10-28 16:30:08 +08:00
姚章浩 6ca8481e8e docs(release): 台账补记 0.1.126 与真实服务验收结论
build / build (push) Successful in 1m11s
为什么:候选 SHA 与 CI/验收结论要落在台账里,便于父审独立核对。
2026-10-28 16:00:16 +08:00
姚章浩 8da4666e76 test(library): 端到端编排在本机真实 PG/ES 上跑通
build / build (push) Successful in 1m14s
为什么:只验证单阶段写入还不够,完整链路(授权→预备份→四类写入→校验)也要在真实服务上跑。
本机 WeKnora 测试栈一次性库与随机索引已通过,CI 无这些服务故在 test-scope 声明为可选。
2026-10-28 15:58:06 +08:00
姚章浩 281699a431 feat(library): 生产整库真实读写/校验/恢复与本机真实服务验收
build / build (push) Successful in 1m21s
为什么:生产目标此前是"无条件失败"的占位,编排再完整也不能落地;本轮把它做成真实实现,
并在本机 WeKnora 测试栈的真实 PG/ES 上跑通导入、撤销、预备份与故障恢复。

- ProductionLibraryTarget:Current 读行数+身份摘要;Apply 按阶段真实写入(database/wiki
  同事务先删本库再插、resources 传对象、index 搬运原始文档与向量);Verify 比对身份摘要;
  Restore 按预备份快照回放。
- 撤销真正生效:索引阶段写入后 PruneScope 删除快照里没有的文档。
- ProductionBackupStore:导出前独立完整预备份,导出前后行数不一致即失败。
- worker 用注入配置装配目标与预备份;缺项把变量名写进阶段并失败,不再有占位对象。
- capacity 安全加法防溢出;保留策略组合语义明确(各维度都超限才候选)。
- 新增本机真实服务集成测试(一次性 kg_library_test_* 库 + 随机 ES 索引),
  覆盖导入/校验/撤销/预备份/故障注入/恢复。
2026-10-28 15:54:01 +08:00
姚章浩 444b820210 docs(release): 台账补记 0.1.123 与 run164 失败说明
build / build (push) Successful in 1m10s
为什么:run164 的失败原因与修复要留在台账里,不能让失败消失。
2026-10-28 15:32:52 +08:00
姚章浩 7e3075f0fc docs(release): 台账补记 0.1.121–0.1.122 与候选说明
build / build (push) Failing after 48s
为什么:每个开发版本的 SHA 与 CI 结论要能独立审计,不留在交付报告里。
2026-10-28 15:17:48 +08:00
姚章浩 2038462dbb feat(library): Wiki 页面级差异与页面展示
build / build (push) Successful in 1m24s
为什么:撤销文档后 Wiki 变化此前只有"快照保存"证据;页面级差异能给出增/改/撤,
但必须写明它不等于实体、概念、边与引用的语义重算。

- overview 差异新增 wiki 字段:读两份快照的 wiki_pages 行数组比较,缺部件时 unknown。
- 发布管理"本次变更"显示 Wiki 新增/更新/撤销页数与页面级声明。
- 真实归档验证:对内 vs 对外 = 新增 31 / 撤销 1,反向镜像一致。
2026-10-28 15:15:33 +08:00
姚章浩 7b8cc0384b feat(library): 生产发布授权与同步/恢复编排,worker 接线
build / build (push) Successful in 1m19s
为什么:同步入口此前只会拒绝;授权、预备份、四类写入、验证与失败恢复都没有代码。
本轮把编排与授权实现出来,真实生产执行仍按授权闸口关闭。

- internal/libraryrelease:预备份 → 数据库/资源/索引/Wiki → 验证 → 失败恢复;
  授权过期/目标不符/快照不符/参数变化在动生产前拒绝;恢复失败单独上报。
- 迁移 000067 library_release_authorizations(目标+快照+参数摘要+有效期,可撤销)。
- POST/GET /api/v1/library/authorizations 与 /{id}/revoke,仅管理员签发,5–1440 分钟。
- worker library.sync 接编排:先闸口再授权,阶段落库,只有编排成功才标成功。
- 生产目标适配器未配置时显式失败(不返回零值状态),预备份存储同。
- overview 返回授权状态,页面"生产保护与恢复"显示签发人与有效期。
- 验收矩阵区分"已实现·隔离测试通过"与"真实执行未授权"。
2026-10-28 15:11:27 +08:00
姚章浩 12bb839d8a docs(release): 台账补记 0.1.116–0.1.118 的 CI 结论
build / build (push) Successful in 1m15s
为什么:版本台账要能独立审计每个版本的 SHA 与 CI 结论,不能只留在交付报告里。
2026-10-28 14:43:22 +08:00
姚章浩 e313ae12f2 feat(library): 真实差异比对 + 孤儿作业回收 + 验收矩阵与台账
build / build (push) Successful in 1m18s
为什么:发布管理的差异一直显示"未接入",业务作业也可能因队列丢作业而永久占锁;
本轮把这两件做成真实实现,并把 30 个验收项与 CI/版本身份落成可审计文档。

- internal/librarydiff:稳定文档 ID + 排除易变列的整行摘要做增改撤销;overview 在
  生产权威版本与测试封存都在时给出 computed 差异并标注两侧快照,缺一侧仍是 unknown。
- 发布管理"本次变更"列文档身份与比对版本;无差异时明确写"没有文档差异"。
- ReconcileLibraryJobs:worker 启动清理队列已无对应作业的孤儿,避免永久互斥。
- docs/library-acceptance-matrix.md 与 docs/release-ledger.md 固定证据与版本事实。
2026-10-28 14:39:17 +08:00
姚章浩 58d0483d44 feat(release): 按确认稿重做发布管理(两库页签 + 单作业区 + 版本抽屉)
build / build (push) Failing after 19s
- 顶部只有对内/对外两个知识库页签,切换只渲染选中库的一个作业区
- 同步前:生产当前→拟发布、新增/更新/删除与 Wiki 完整快照、准备清单、
  版本与快照/启动同步、本次变更 + 生产保护与恢复、五段作业阶段
- 同步中:作业阶段/版本/已用时 + 四类数据卡 + 同步明细与异常,未接入项
  只显示状态、不编百分比
- 版本与快照抽屉按 scope 列全部封存记录与分项摘要,恢复入口在真实演练前禁用
- 存储口径移到系统设置→存储管理只读区;保留/清理/自动清理标未接线
- 生产段无真实数据一律"未接入/尚无记录",启动同步禁用并说明
2026-10-28 13:23:55 +08:00
姚章浩 910c0fcd76 feat(library): 页面发起封存的最小权限主机侧执行边界
build / build (push) Failing after 16s
- libraryagent:0700 目录内 0600 unix socket + SO_PEERCRED 同 uid,
  固定 binary/归档/日志/项目/写入者声明,请求只能选范围与作业号
- API:POST/GET /api/v1/library/captures(入队 library.capture + 读队列状态)
- worker:library.capture handler 走 socket;启动先 recover;不挂 Docker socket、
  不持有来源凭据
- web:整库版本页显示真实作业状态并可发起封存(未接线/无权限给出原因)
- scripts/library-agent-install.sh:构建、安装 systemd user 服务并读回权限
2026-10-28 12:11:08 +08:00
姚章浩 c5e033dd0e fix(library): 幂等重放、跨版本去重用量与契约复核
build / build (push) Failing after 15s
- 同作业号重跑读持久日志+已封存清单按首次请求重放(source_read=false,
  writers_stopped=false),换范围/绑定库或清单校验失败即拒绝
- 归档用量按跨版本去重统计内容字节,清单/未引用 blob/实际分配单列,
  不再用各版本 unique 之和冒充归档占用
- 冻结窗口内复核来源契约,不一致即中止;拒绝组外同镜像运行容器
- 补齐整库只读接口的 OpenAPI 契约
- 按用户确认取消 A06:文档/页面/接口不再把效果测试列为封版门槛
2026-10-28 12:01:09 +08:00
姚章浩 045fc44f0e feat(library): 接通整库封存真实入口与只读版本页
build / build (push) Failing after 19s
- libraryctl:status/preflight/capture/list/verify/recover,只读来源会话、
  结清检查、契约推导、维护窗口内停写者、导出十表+索引+对象+Wiki、封存候选后恢复
- libraryfreeze.ProjectController:按 Compose 项目逐服务声明固定身份,
  未声明服务/容器重建即拒绝,仅 SIGTERM,恢复后必须 healthy
- httpapi:GET /api/v1/library/snapshots[/{id}] 只读列出封存产物
- web:「整库版本」页(只读、未接线显示原因、无假按钮)
- api:LIBRARY_ARCHIVE_ROOT + 归档根只读挂载
- docs/library-capture.md:运维手册与 2026-10-28 两次真实封存实测

不代表整库业务验收通过:内容/问答/性能验收、跨系统切点、Wiki 删除语义重算、
生产预备份/恢复演练/双闸/同步协调仍未完成。
2026-10-28 11:39:50 +08:00
姚章浩 864a1632e0 test(snapshot): 增加真实测试源只读诊断并记录孤立索引阻塞
build / build (push) Successful in 1m11s
2026-10-28 08:32:23 +08:00
姚章浩 9f990158b9 test(snapshot): 联合验证真实隔离存储与暂停恢复链
build / build (push) Successful in 1m8s
2026-10-28 08:22:21 +08:00
姚章浩 76f687b52d feat(snapshot): 按已确认口径保留索引产物并标记验收策略
build / build (push) Successful in 1m12s
2026-10-28 08:01:46 +08:00
姚章浩 bf049cee1f fix(snapshot): 暂停前后强制检查共享源全库任务
build / build (push) Successful in 1m12s
2026-10-28 01:05:44 +08:00
姚章浩 2c5ecdf0f3 fix(version): 补齐整库基础版本的独立快照说明
build / build (push) Successful in 1m11s
2026-10-28 00:34:20 +08:00
姚章浩 9105f8316c feat(snapshot): 增加整库独立快照及测试停写恢复基础
build / build (push) Successful in 1m20s
2026-10-27 23:44:12 +08:00
姚章浩 c1d49adb62 docs(agents): 补三Agent交接闭环工作流(状态机、快照tag与验收模板)
build / build (push) Successful in 53s
工单 #6。三个会话(架构/开发/验收)在同一仓库串行协作时缺一份可核对的
书面契约,口头约定会漂移:状态标签命名不统一(仓里还留着 status/working)、
快照 tag 被当正式版本用、验收拿开发总结当结论。

本文件把契约写死:角色边界、main 单线串行、六个状态名与 blocked 要素、
快照 tag snapshot/vX.Y.Z 与正式版本 vX.Y.Z 分线、不可变与回退规则、
开发与验收交接模板、关单条件、安全边界。

为什么快照不能沿用 vX.Y.Z:make verify 里的 check_changelog --tags 会对每个
正式 tag 断言其提交内 CHANGELOG 顶部版本一致;在临时仓库实测正式 v0.1.1 与
v0.1.2 都判红,而 snapshot/v0.1.1 被归为非正式 tag 通过。故快照改用
snapshot 前缀,正式版本仍走单独批准与 CHANGELOG 流程。

范围:仅新增这一个文档,不改 CHANGELOG 与 CI。
验证:make verify 15 项全 success(档案 local,退出码 0)。
2026-10-14 17:33:22 +08:00
姚章浩 64b32c2da6 feat(settings): 数据源配置默认只列 PP 资料 + 「可加入的来源」
build / build (push) Successful in 53s
主人要求:AList 已由钉盘文件替代、钉钉知识库改为按当前账号发现 ⇒ 这两类不再出现在
数据源配置里,只保留 PP 资料与「可加入的来源」(按当前钉钉账号列出可访问的知识库与钉盘空间)。

实现(非破坏、可逆):
- 新增纯函数 splitSourceConnections(connections, showLegacy):把 alist 与 dingtalk 归为
  legacy,默认只返回 PP/手工等其余来源;showLegacy=true 时原样返回(顺序不变、一条不少)。
- 「数据源配置」新增一行折叠开关「显示 AList/钉钉知识库来源(N)」:默认收起,只显示
  PP 资料(+手动导入);展开后这 N 个来源照旧可改刷新周期、启停、看修订历史。
  **没有删除任何来源,也没有动它们的调度与目录快照。**
- 区块文案说明登记路径:新来源只从「可加入的来源」勾选登记(钉盘文件已替代 AList)。
- 测试:splitSourceConnections 直测(默认折起 a/d、展开 a/b/c/d)+ SSR 断言默认视图只含 PP、
  折叠项不在默认视图(原断言的 dingtalk 卡片标记改为折叠后的期望)。

验证:make verify 15/15;SystemSettings 用例 7/7。
2026-10-14 12:01:47 +08:00
姚章浩 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
姚章浩 ad9c5d20d4 Merge remote-tracking branch 'origin/main'
build / build (push) Successful in 49s
2026-10-13 19:08:54 +08:00
姚章浩 3a1d32da8f docs(readme): 重写平台定位与能力边界并归档原说明
build / build (push) Successful in 49s
2026-10-13 17:53:23 +08:00
t 950c1b429b feat(release): 抬档门禁(破坏性 ⇒ 0.x 抬次 / ≥1.0.0 抬主)+ 1.0.0 前置(稳定契约承诺)
原来预检只"打印破坏性条数",抬哪一档靠人自觉 ⇒ 改成硬门禁:

- **抬档门禁**:破坏性条数取自**目标提交里候选版本那一段**(发版事实看提交,不看工作区)。
  `0.x` 阶段有破坏性 ⇒ 必须抬**次版本**(或主版本),只抬修订 ⇒ 阻断并报出该抬到哪一档;
  `≥1.0.0` 之后有破坏性 ⇒ 必须抬**主版本**,抬次/修订都阻断。
  另加"候选号必须大于上一个正式版本"(版本只增不减)。
- **1.0.0 前置**:上一版本是 `0.x` 时,候选 `1.0.0` = 对外承诺契约稳定 ⇒ 必须先有写实的
  《稳定契约承诺》(覆盖 接口 / 数据模型 / 配置 / CLI 输出与退出码,不留 `待填`/`TODO`/`TBD`),
  **且候选发布记录里链接它**,缺任一条 ⇒ 阻断;`1.x → 2.0.0` 不重复要求。
- 预检新增"版本走向"事实行(上一版本 → 候选 / 抬哪一档 / 破坏性 N 条 ⇒ 要求哪一档 ✅⛔);
  `docs/RELEASE_RUNBOOK.md` 新增"抬哪一档"对照表与《稳定契约承诺》段(故意留占位)。

验证:release_preflight --self-test 32 分支全过(含新增 9 条);反向夹具新增 §24 六条真实临时仓库用例
(0.x 只抬修订 ⇒ 阻断、0.x 抬次 ⇒ 放行、≥1.0.0 抬次 ⇒ 阻断、1.0.0 留占位 ⇒ 阻断、写实+链接 ⇒ 放行、
写实但没链接 ⇒ 仍阻断)⇒ 夹具 91/0;make verify 15/15。
于是 0.1.0 之后的第一个提交(本提交)开发号 = 0.1.1。
2026-10-13 17:13:44 +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
姚章浩 97f4b29661 fix(version): 就绪看目标提交的顶部版本 + 禁用自动发布路径
build / build (push) Successful in 52s
补两处残余(本轮不推、不打真实 tag、不部署;全部用临时仓库与 spy 验):

1) version_in_commit 必须比"目标提交 CHANGELOG 的**顶部**已发布版本",不是"历史里出现过":
   - 新增 top_version_in_commit(),version_in_commit() 直接比它;预检输出把该提交的顶部版本打出来
     (诊断可审计:例如"顶部是 `## [0.3.0]`,不是 0.2.0"),阻断信息里也带上真实顶部版本。
   - 顺带兜住"最新在最上面 / 版本只增不减"的排序要求。
   - 负例(自检 + 夹具各一遍):顶部 0.3.0、历史里 0.2.0 ⇒ 候选 0.2.0 拒绝;候选 0.3.0 通过。

2) 按只读约定**禁用 --execute 自动发布路径**:
   - 删掉写路径(打 tag / 推 tag / 建 Release 的代码整段移除),`--execute` / `--approve-version` /
     `--approve-sha` 只保留参数兼容(不报 unknown argument),传了就**明确拒绝**(rc=2)。
   - 拒绝发生在收集事实之前 ⇒ **零写操作、零 API 调用**;自检用 spy 断言拒绝路径
     "一个子进程都没跑、一个 API 都没发",并扫描生产路径不许出现直接执行 git 写命令的行。
   - 输出里也不再推荐自动路径;RELEASE_RUNBOOK 第 4 步改为由人执行。

验证:release_preflight --self-test 绿(就绪 20 分支 + 顶部/历史两态 + --execute 拒绝零写 +
源码写路径扫描);反向夹具 84/0(含 --execute rc=2 且跑完仓里 tag 不变);make verify 15/15 success。
2026-10-13 15:48:50 +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
姚章浩 4f8ee0a8c5 feat(version): 变更记录改固定四类,破坏性变化带发布文档链接
按项目方给的规则重做 CHANGELOG 与检查器(本轮不推、不打 tag、不部署):

- 条目形状:四个自定义必填节(改了什么/兼容性与迁移影响/怎么验收/已知问题)
  → 固定四类 Added/Changed/Fixed/Security;分类可省略,**不许空栏目**;每个版本块
  至少一个非空分类;`[Unreleased]` 段不得写日期。
- 破坏性变化:固定写在 Changed 里、以 `**破坏性**` 开头,**必须链接发布文档**;
  仓内 `docs/….md` 链接**对账**(不存在 ⇒ 红)。写在别的分类里也判红。
- 结构判据补强:非法日期、重复版本、顺序必须严格递减、前导零;围栏代码块里的
  `##`/`###` 不算标题;**块正文以任意二级标题为界**(「## 能力基线(未发布)」这类
  不带方括号的标题原先不是边界,会把后文算成上一节的内容 ⇒ 空栏目被兜成"有内容",
  夹具真的抓到了这个假绿)。
- tag 一致性:远端专属清单 `.ci-remote-tags` → `ls-remote` → 本地 refs 三级来源;
  **没核对远端就不许说校验成功**(CI/预检用 `--require-remote`,拿不到清单即红),
  非正式 tag(如本地 `x`)不当作发布版本、不判红,没有 tag 不判红。
- `0.1.0` 定为**未发布的能力基线**(远端无 tag、无 Release):改成不带方括号的
  「## 能力基线(未发布)」,不补造 tag/发布日期/发布事实。
- 过程记录迁出:审查轮次与命令输出 → `docs/archive/CI_接入留档.md`(原文保留);
  未合并事项 → `docs/BACKLOG.md`;补 `docs/RELEASE_RUNBOOK.md`(破坏性变化的链接目标,
  同时修掉 CHANGELOG 里指向不存在文档的悬空引用)。
- 发版预检:无版本基线时把「首个正式版本号由人指定」列成阻塞项(不再要求先有基线才能跑);
  打印破坏性变化条数;远端 tag 查不到时明说未核对并阻塞;不再硬读已删除的『已知问题』节。
- traps trap-5 口径修正:不是"不用 tag",而是"构建时不要用 git describe 取版本号"。

验证:check_changelog --self-test 全过;反仓夹具 80/0(含五条真改文件的结构负例、
六条只在临时仓库里造 tag 的场景);make verify 15/15 success。
2026-10-13 14:59:35 +08:00
姚章浩 9ad13a8c62 docs(agents): 留档短命试验分支的真实 CI 运行记录(run #16 失败 / #17 通过)
build / build (push) Successful in 48s
记录真实 run 链接、完整 SHA、14 项必需检查、预览 URL 与探测结果、通知入队与 ret=-2 的送达状态、run #16 暴露的 total_commits=0 缺陷,以及未验证项。分支 ci-trial-20261012 已在试验后删除,远端只剩 main。
2026-10-12 17:00:21 +08:00
姚章浩 257a8a187a fix(gate): total_commits<=0 视为未声明,范围以 Git 为准(真实新分支事件形状)
build / build (push) Successful in 1m22s
真实 Runner 首跑(run #16, ci-trial-20261012)发现:新分支推送时 Gitea 给 total_commits=0、commits 列表被截断(5 条),而 Git 范围 12 条;原先'声明条数 != Git 范围就拒绝'让门禁第一次真实运行就红,且与代码质量无关。改成 Git 范围是唯一权威:total_commits>0 才交叉核对(保留'声明6/列表1/Git2 ⇒ 拒绝'的判据),<=0 或缺失视为未声明并以 Git 范围为准,同时把'负载条数不可信、列表被截断'写进来源说明。--self-test 增加该形状(15 分支);traps 22 记录教训:负例要照真实负载的形状造。
2026-10-12 16:55:41 +08:00
姚章浩 91b674f278 docs(agents): 清掉 traps.md 文件末尾的多余空行(git diff --check 报的最后一处)
build / build (push) Failing after 4s
2026-10-12 16:52:44 +08:00
姚章浩 d167a2b937 fix(ci): 第一步改最小 bootstrap(空工作区可启动),新分支范围以默认分支为基准
空工作区启动:workflow 第一步不再调用仓内脚本(此时它还不存在,实测 rc=127),改成只用 runner 环境变量的最小 bootstrap:校验协议/凭据 → git init+remote add → 取事件完整 SHA → checkout → 断言 HEAD==事件 SHA;检出后才调用 ci_checkout.sh --post-checkout 做再断言与补取(默认分支引用、事件 before)。按任意 SHA fetch 在服务端默认不可用(服务器不允许请求未公开的对象),改成取分支历史 + --deepen 再核事件 SHA。新分支范围:after --not --remotes 会把刚 fetch 的同名远端分支(=after)当排除集、排除自身得到 0 条;改为以远端默认分支为基准 refs/remotes/origin/<default>..<after>,取不到基准就明确失败。新增 scripts/ci_chain_fixture.py:本地 HTTP git 服务 + 把 workflow 的 bootstrap run 块原样抽出来在空目录执行,正/负例 12 项断言。
2026-10-12 16:45:10 +08:00
姚章浩 75a6085844 docs(agents): traps 14-18(假检查/预览顺序/观察到的 context/协议降级/范围退化) 2026-10-12 11:15:18 +08:00
姚章浩 96ac67e744 docs(agents): traps 补 5 条、CHANGELOG 记这批 CI 改动的改了什么与怎么验收
新增陷阱(只追加、不复用旧号):
trap-8 gofmt -l 列出文件时退出码仍是 0(只看退出码=假绿);
trap-9 tar -C dir . 产生 . 根成员 ⇒ 预览 404 而流水线显示成功;
trap-10 依赖清单被静默改写(-mod=mod、缺锁回落 npm install);
trap-11 汇总把"未运行/被跳过"当"通过";
trap-12 事件 SHA 与检出 SHA 不一致;
trap-13 密钥扫描规则过宽 ⇒ 永远的假红,最后被人整条注释掉。
(trap-13 来自实做:第一版规则用很宽的字符类抓口令段,把 postgres://{u.quote(...)} 这类
 模板表达式和 *** 脱敏也当成密钥,四条全是假阳性。收窄口径并把这些形状钉进良性对照防回归。)

AGENTS.md 的现状表需要同步(写着"main 已启用分支保护",实测 GET /branch_protections = [])——
该文件受写入保护,本次未改,修正稿另存待批准后应用。

验证:python3 scripts/check_changelog.py 结构自检通过;make verify 全绿。
2026-10-12 00:11:10 +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
姚章浩 ed60b20790 docs(acceptance): 补 C9 数据源配置(列出来→勾选→变成来源)实测记录 2026-10-10 16:47:06 +08:00
姚章浩 f8faed0b21 feat(release): 发布到生产一键完成(建批+锁定+授权合成一个动作)
用户的反馈是对的:上一轮只砍掉了"冻结/锁定"两个按钮,但一条文档从"已批准"到"发出"实际仍要
~9 次交互(找条目→勾选→填名→建批→滚到清单→找批次→授权→输语句→提交),因为"批次仪式"本身
还留在路径上。

- 新端点 POST /api/v1/release-batches/publish-now:在同一操作里先建批(SealImmediately,冻结
  即锁定)再授权生产发布,返回 {batch, execution}。
- 不放松任何约束:请求门禁(403)、备份恢复验证门禁(412)、管理员身份(401)、固定确认语句(400)、
  发布前快照硬门禁(409)、建批门禁(422) 全部沿用既有实现,无旁路;名称可省(服务端固定回退名,
  保证重放指纹一致)。
- 界面:审核行上直接「发布到生产」(管理员 + 门禁开启时显示),工具栏支持选中多条一次发布;
  复用既有确认弹窗(固定语句)。批次名不再是必经步骤。
- 实测(面板):点行级按钮 → 输 `publish-to-production` → 提交 → 批次 rb-bee80550…(sealed)
  + 执行 rx-3d831129…(completed, 项级 verified) → 页面回滚 rx-43835fac…(rolled_back, absent)。

测试:publish_now_test.go(四道关卡 + 组合调用 + 名称回退)与 reviewRelease.test.ts(请求体/回传/错误原因);
Go 26 包、前端 183 条全过。
2026-10-10 15:48:30 +08:00