From e735ae603dcda10219f5a6e99493baf5aa21cdc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9A=E7=AB=A0=E6=B5=A9?= Date: Wed, 4 Nov 2026 16:20:09 +0800 Subject: [PATCH] =?UTF-8?q?docs(version):=20=E8=A1=A5=E9=BD=90270=E6=9D=A1?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=BC=80=E5=8F=91=E7=89=88=E6=9C=AC=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按权威 version→commit 映射,为缺失开发版本增加 exact-SHA CHANGELOG 块。 正文采用该提交新增 CHANGELOG 或真实 commit body/diff 归纳,已有82条不改。 新增270条绑定验收与错SHA拒绝测试,不改变版本计算与同步实现。 --- CHANGELOG.md | 4992 +++++++++++++++++ .../development_notes_history_test.go | 54 + .../development_notes_plain_test.go | 4 +- .../testdata/development_notes_history.json | 1082 ++++ 4 files changed, 6130 insertions(+), 2 deletions(-) create mode 100644 internal/versionhistory/development_notes_history_test.go create mode 100644 internal/versionhistory/testdata/development_notes_history.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f807c79..e51dab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 开发快照 0.1.353(补齐逐版本开发说明) + +### Docs + +- **按权威 version→commit 映射补齐 270 条历史开发版本说明**:每条使用独立 + `## 开发快照 snapshot/v - <日期>` 与 `目标提交:`,正文来自该提交 + 自身新增的 CHANGELOG 段,或按真实 commit body / name-status / numstat 归纳的文件与统计; + 测试/文档 only 提交明确标注不新增产品功能。 +- **已有 82 条开发说明未改写**;109 条 historical 记录不伪造版本号。新增 exact-SHA 验收覆盖 + 270 条目标块、错 SHA 拒绝与全 352 开发记录非空;未改版本计算、正式版基线与同步实现。 + ## 开发快照 0.1.352(版本说明标题格式兼容) ### Fixed @@ -2246,6 +2257,7 @@ - 删除问答记录、同步栏和区域页中的冗余解释文字、读取时间与源连接提示,保留真实错误、未同步、权限阻断和安全告警。 ## 开发快照 snapshot/v0.1.28 - 2026-10-18 +目标提交:f33730ed7437536c3d1e01cc2f20231af076b531 ### Added @@ -2585,3 +2597,4983 @@ - **验收口径**:`docs/USER_FLOWS_ACCEPTANCE.md`(C1~C9)与 `docs/OPERATIONS_RUNBOOK.md`。 ⚠️ 上面的端口与容器名前缀是**本机运行栈**的实测值;换环境要按那边的运行栈改,别无脑照抄。 + + +## 开发快照 snapshot/v0.1.349 - 2026-11-04 +目标提交:0d9091e4644cf3eb053fe2c852bdbab90a51cc24 + +### Fixed +- **fix(library): 目标隔离映射兼容旧计划与真实回归** +空 mode 计划不映射;索引 source_id 按语义映射;relation 按字段表解析。 +隔离回归按持久映射 ID 断言,DS02 故障注入真实命中。 + +改动范围:`internal/releaseadapter/v071/library_target_ids.go`、`internal/releaseadapter/v071/library_target_path.go`、`internal/releaseadapter/v071/library_verify_content.go`、`internal/releaseadapter/v071/data_closure_r10_test.go`、`internal/releaseadapter/v071/data_contract_r10_test.go`、`internal/releaseadapter/v071/data_matrix_r11_test.go` 等 15 个 +统计:15 个文件,+428/-91。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/data_closure_r10_test.go`、`internal/releaseadapter/v071/data_contract_r10_test.go`、`internal/releaseadapter/v071/data_matrix_r11_test.go`、`internal/releaseadapter/v071/data_schema_r11_test.go` 等 12 个 + +## 开发快照 snapshot/v0.1.348 - 2026-11-04 +目标提交:e0fc2c32b89b7ac74f2346057b43436f9404ba3a + +### Changed +- **feat(library): 目标库隔离 ID 映射并保留旧库** +- **同一源快照可导入同实例的第二个知识库**:写入目标前按 `sha256(目标 tenant + 目标 KB + 表 + 源身份)` + 派生目标侧 UUID(knowledges / chunks / knowledge_tags / wiki_pages / wiki_folders / + wiki_page_issues / resource_bindings)。同一目标重复同步、重试与跨版本得到同一 ID,不同目标 + 或不同表得到不同 ID;旧库原有行不再与本次写入撞主键,也不会被删除。 +- **引用字段一起改写**:chunks 的 parent/pre/next 与 relation/metadata 中的精确身份、 + wiki_pages 的 chunk_refs / source_refs(保留 `id|title` 与顺序、重复)、folder_id、 + resource_bindings.owner_id、索引文档物理 ID 与 chunk_id/knowledge_id/source_id/tag_id; + 正文、标题、向量、引用顺序与重复项保持不变,不重新解析、不重新 Embedding。 +- **resources 保持原 ID**:资源是多知识库共享的字节载体,继续走既有"内容相同 + 来源可审计"复用 + 与归属证明;只改绑定行的 id/owner_id,不改 resource_id。 +- **写集合、校验、索引计划与恢复共用同一映射**:写集合登记目标身份,verify 按目标身份比对; + 恢复重放目标写前镜像时不二次映射。引用指向未登记身份时直接拒绝,不做模糊替换。 + +改动范围:`internal/libraryrelease/writeset.go`、`internal/releaseadapter/v071/library_index_plan.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/library_scope_remap.go`、`internal/releaseadapter/v071/library_target_ids.go`、`internal/releaseadapter/v071/library_target_path.go` 等 13 个 +统计:13 个文件,+1324/-5。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_target_ids_integration_test.go`、`internal/releaseadapter/v071/library_target_ids_test.go`、`internal/releaseadapter/v071/library_target_real_seal_test.go` + +## 开发快照 snapshot/v0.1.347 - 2026-11-04 +目标提交:604295549840dd2f4fbe2f91f4ac74d529cc5ca2 + +### Fixed +- **fix(objectgateway): 入口测试夹具改用无口令 PG URL** +现象:CI run 347 / job 362 在 security-secrets 失败,指向 +cmd/object-gateway/main_test.go:30 的 pg-url 类别。 + +根因:该 DSN 只用于 127.0.0.1:1 故意不可达连接,验证启动能越过 +配置阶段到达 journal/后端准入,本身不需要任何口令,却写成了带口令形式。 + +取舍:直接换成合法无口令 URL,不拆秘密字符串隐藏检测、不改扫描器/基线/ +必需项范围。产品代码与测试行为不变。 + +证据:check_secrets.py 由 exit 1(指向该行)转为 exit 0;go test +./cmd/object-gateway 通过;umask 022 下 make verify 16 项本地必需全 success。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`cmd/object-gateway/main_test.go` +统计:1 个文件,+1/-1。 + +### Tests +本次提交包含测试/夹具变更:`cmd/object-gateway/main_test.go` + +## 开发快照 snapshot/v0.1.346 - 2026-11-04 +目标提交:a55319d9726f063baf43b7fe1e3fb7768946a4d8 + +### Fixed +- **fix(objectgateway): 取消整桶独占前置并按维护停写更新** +- **对象网关不再要求整桶独占证明**:`Gateway.Ready` 只核 journal、PG 持久化设置与后端版本能力, + 删除 `Isolation`/`AuditVerifier` 与 `OBJECT_GATEWAY_AUDIT_FILE`、`OBJECT_GATEWAY_AUDIT_PUBLIC_KEY`、 + `OBJECT_GATEWAY_ID` 的启动与逐请求校验;旧部署残留值被忽略,不再阻塞默认共享桶路径。治理 PG + SELECT-only authority、plan/owner/attempt/epoch 绑定、intent-before-send、单次写与精确回滚不变。 +- **目标 KB 更新走维护窗口**:停目标测试 WeKnora 写入应用容器并读回确认 → 备份本次目标 KB/对象范围 + → 用原 plan/WriteSet 更新 → 启动原容器 → 健康与页面验收;失败按原备份恢复。不再要求排空整桶 + 全部 writer、RAM 权限清单或独立只读 OSS AK,现有 AK 继续用于 PUT/GET/DELETE/列版本。 + +- **对象网关不再要求整桶独占证明**:启动只核 journal、PG 持久化设置与后端版本能力,旧部署残留的 + `OBJECT_GATEWAY_ID`、`OBJECT_GATEWAY_AUDIT_FILE`、`OBJECT_GATEWAY_AUDIT_PUBLIC_KEY` 被忽略,不再阻塞共享桶路径。 + 治理 PG SELECT-only authority、plan/owner/attempt/epoch 绑定、intent-before-send、单次写与精确回滚不变。 +- **更新方式改为维护停写**:停目标测试 WeKnora 写入应用容器并读回确认 → 既有维护/预备份链记录目标 KB 范围 → + 原 plan/WriteSet 经正式 Apply 更新 → 启动原容器 → 健康与页面验收;失败走既有恢复链回放原预备份。 + 不再要求排空整桶全部 writer、RAM 权限清单或独立只读 OSS AK,现有 AK 继续用于 PUT/GET/DELETE/列版本。 + +- **共享桶不再因同 key 外部写者误报成功**:create 后除精确版本读回外核对当前 latest;并发写者产生更新的当前版本时, + 明确返回冲突并保留双方版本,不做任何未记账的删除,也不会在 Reconcile 里把已被外部写者接管的 key 报成 committed。 + 版本化存储的 PUT 不是原子 create-if-absent,同 key 并发不自动合并,需按回执人工处置。 + +改动范围:`cmd/object-gateway/main.go`、`internal/objectgateway/attestation_legacy.go`、`internal/objectgateway/audit.go`、`internal/objectgateway/create.go`、`internal/objectgateway/gateway.go`、`internal/objectgateway/http.go` 等 25 个 +统计:25 个文件,+510/-171。 + +### Tests +本次提交包含测试/夹具变更:`cmd/object-gateway/main_test.go`、`internal/objectgateway/acquire_integration_test.go`、`internal/objectgateway/audit_test.go`、`internal/objectgateway/create_test.go` 等 14 个 + +## 开发快照 snapshot/v0.1.345 - 2026-11-03 +目标提交:017765763a74919fdcc0591454ae85d3415f6a43 + +### Fixed +- **fix(library): 维护卡只展示 owner_job_id 匹配的作业详情** +问题:维护窗口 record.owner_job_id 指向失败/待恢复的作业 A,但页面把同 scope +最新作业(更新的成功封存 B)当窗口进度,needs_restore 被显示成“导出 · 已完成”, +需要处理的原窗口因此看起来已经结束。 + +改法: +- maintenanceSummary 只接受“按 owner_job_id 读到的持有作业”,id 不一致一律按 + 未读取处理;进度/错误/恢复说明只来自该作业,不拿最新作业兜底。 +- 新增 fetchLibraryJobDetail 读取既有只读接口 GET /api/v1/library/jobs/{id}, + 明确区分 loaded / missing(404) / error(非2xx、结构不符、id 串包) / unread, + 不把读取失败吞成“不存在”。 +- 恢复可用性只由“维护仍持有 + 本窗口作业未收口 + 当前 scope 等于 owner_scope” + 决定;running/pending 不并发恢复,已释放/无记录不提供恢复,跨 scope 给切换提示。 +- 入口状态明确标注“维护记录,未实时探测”;held 时说明只有维护记录释放才算结束; + held 但持有作业已完成时写“维护记录未对账”,不提供强制成功。 +- load 增加请求代次守卫,晚回包不覆盖更新的窗口;作业详情返回的 id 与请求不符 + 按读取失败处理。 + +未改后端准入、网关协议或任何维护记录;未部署。 + +改动范围:`web/src/ReleaseTargets.mount.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts`、`web/src/releaseTargetsMaintenance.test.tsx` +统计:4 个文件,+545/-27。 + +## 开发快照 snapshot/v0.1.344 - 2026-11-03 +目标提交:bd452e85b3b40083ac13c35cd9afa04dd978c714 + +### Tests +- **test(library): 维护 scope 终态按正式释放语义断言 released** +ReleaseLibraryMaintenance 的真实终态是 state=released + released_at,原断言写成 verified 会误红;改为按生产语义核对 released/open。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`cmd/worker/library_three_blockers_chain_test.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.343 - 2026-11-03 +目标提交:71f23c67639f3ea8f0171a56d0198ac815ca216a + +### Tests +- **test(library): 用结构化 URL 构造 reader DSN 夹具** +保持同一解析断言,避免源文件出现字面带口令连接串被密钥扫描器误报。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`cmd/worker/library_three_blockers_chain_test.go` +统计:1 个文件,+6/-1。 + +## 开发快照 snapshot/v0.1.342 - 2026-11-03 +目标提交:b4add6c9b0912294ceb9e00a8d54a4bd955f9135 + +### Fixed +- **fix(library): 收口 B1-R1/B2-R1/B5-R1 并让恢复链经过正式队列终态** +- **B1-R1 受限演练恢复计划**:写集合新增 `restore-drill` 种类——共享 V2 信封、contract 1 与 schema 摘要, + 对象/索引/本地 ID 映射必须为空,来源 tenant/KB 必填;任一项非空即拒绝,旧执行器不能把它当发布计划 + 接管。恢复演练入口 `RestoreDrill` 与对象网关签发的零对象计划使用同一 attempt/写前镜像身份。 +- **B2-R1 verify 作业的不可变 operation**:`POST /api/v1/library/verifications` 在创建作业前先建 + operation(`ensureLibraryIntent`),作业携带 `operation_id` 与稳定幂等键;同一幂等键对应不同请求返回 + 409。worker owned attempt 与同 job recover 的网关 claim 都按原 operation + recovery job/operation + 核验,缺 operation 的 verify 作业在真实恢复中 fail closed。 +- **B5-R1 迟到响应不得污染新 scope**:恢复验证面板按 scope 世代 + 动作序号判定在飞动作;切 scope 即作废 + 旧 POST/回调,迟到响应不能改写新 scope 观测、不能触发父级旧 job 回调、不能清掉新动作的 pending。 + 新增真实挂载反例用例覆盖旧 internal POST 晚到的场景。 + +改动范围:`internal/httpapi/library_verify.go`、`internal/libraryrelease/writeset.go`、`internal/libraryverify/verify.go`、`internal/releaseadapter/v071/gateway_object_store.go`、`internal/releaseadapter/v071/library_production_target.go`、`web/src/RestoreVerificationSection.mounted.test.tsx` 等 12 个 +统计:12 个文件,+1515/-18。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_three_blockers_chain_test.go`、`internal/httpapi/library_verify_test.go`、`internal/libraryverify/canary_test.go` + +## 开发快照 snapshot/v0.1.341 - 2026-11-03 +目标提交:f9457fab52eb84c7af0edad492aad23fa59658ea + +### Fixed +- **fix(library): 打通恢复验证的正常恢复与页面联动** +- **B1 恢复验证的恢复身份/计划**:共享演练不再把同一份快照同时当作来源与写前镜像 + (真实恢复合同会在任何写之前拒绝)。同一维护切点内做两次**真实**捕获:写前镜像 + (恢复实际回放的预备份)与来源身份(独立密封、内容相同、身份不同);两者与作业/ + 目标/tenant/KB 一起持久化,写集合在第一笔写入之前落盘并登记。演练恢复走限定范围的 + 安全入口 `RestoreDrill`:复用同一份 DS01 身份合同与同一批 Apply 原语,四个阶段按 + 写前镜像整段回放(先清 scope 再插入归档行)并做整实例指纹校验,不是"只删 canary"; + 有恢复派发闸口的目标拒绝该入口。 +- **B2 演练接进治理维护生命周期**:worker 在开窗口前写实例级 + scope 级维护行, + 写入边界更新真实入口状态;成功或"未写内容的安全收尾"才释放占用并开入口,held 时 + 保留行(入口 closed、状态 needs_restore)。同 job 的现有恢复入口贯通 verify: + 恢复队列准入、中断接管与 `library.recover` 的 kind 过滤都接受 verify;预备份引用与 + 写集合摘要按 (scope, job, 写前镜像) 精确登记,恢复入口能取回。失败文案由报告的 + restore_check 与窗口终态产生,不再用"有快照"冒充"已恢复并复核"。 +- **B3 证据优先级统一**:API 与 worker 都以证据为先——明确 failed/invalid 或读取错误 + 必拒绝,即使 `PRODUCTION_BACKUP_RESTORE_VERIFIED=true`;历史 env 兜底只保留在 + "明确没有证据行/存储无证据能力"时。未读取证据的旧调用点语义不变。 +- **B4 恢复验证作业联动父页面**:GET `/api/v1/library/verifications` 从真实业务行 + 投影在飞 verify 作业(running/job_id/state/stage);子面板接受作业后回调父页面 + `onAccepted`,父页面立即 load 并纳入轮询,终态回调 `onComplete` 再做一次读回, + 正常一次点击无需手工刷新即可同步进度/终态/闸口。 +- **B5 观测绑定 scope 与请求代次**:切 scope 立即丢弃旧观测;晚到的旧响应按代次丢弃; + HTTP 失败/不可解析/响应 scope 不一致都明确显示读取失败,保留旧数据时必须标成 + "上次观测已过期(刷新失败)",不再显示"已验证(当前目标)"。 + +- 迁移 79 仍只向上应用;`migrator` 没有自动 down 路径,部署回滚保留 79 与既有证据, + 不执行有损 `down.sql`。F1(备份主机连接/命令状态细分)仍为非阻塞后续。 +- 未部署、未调用业务目标、未切换服务;本轮只做隔离验证与固定 SHA 的候选提交。 + +改动范围:`cmd/librarydrill/main.go`、`cmd/worker/library_recovery.go`、`cmd/worker/library_verify.go`、`internal/httpapi/library_verify.go`、`internal/httpapi/server.go`、`internal/libraryrelease/writeset.go` 等 22 个 +统计:22 个文件,+1145/-136。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_verify_test.go`、`internal/httpapi/library_verify_test.go`、`internal/libraryverify/canary_test.go`、`internal/libraryverify/restore_identity_test.go` + +## 开发快照 snapshot/v0.1.340 - 2026-11-03 +目标提交:5e1482e349e23728f8f2f1aeb770422eba2e5b5f + +### Changed +- **feat(library): 恢复验证成为可页面发起的真实作业** +根因:生产可恢复性只有 PRODUCTION_BACKUP_RESTORE_VERIFIED 这个手填 env 开关, +页面没有任何入口去真跑一次恢复;CLI(librarydrill)又不是 worker 能复用的实现, +所以"已验证"既证明不了跑过、也证明不了跑的是当前目标。 + +改动: +- 共享实现 internal/libraryverify(维护隔离→捕获→基线校验→canary→恢复→复核→关窗口), + cmd/librarydrill 改为薄封装,CLI 与 worker 只允许一份执行体。 +- verify 成为独立业务作业:kind + VerifyStages(capture/verify/canary/restore/finalize), + 与 sync/restore/baseline 共用同一把写互斥;迁移 79 加 kind 约束、重建部分唯一索引、 + 新建 library_restore_verifications(按 target_id 绑定,换库/索引/桶旧证据自动失效, + 同 target+scope+job 幂等)。 +- worker:admitLibraryJob + runOwnedLibraryAttempt 之后调用共享实现,阶段实时写 + library_job_stages,结束后写真实终态;通过写 passed 证据,失败写 failed+原因(不伪成功); + 中断接管按同一作业 id 收掉维护窗口。 +- API:POST/GET /api/v1/library/verifications(管理员;闸口关闭/维护中拒绝;返回完整 + 整库目标、最近证据、失败原因),OpenAPI 同步。 +- 闸口:同步/恢复/续作的证据判断改为读当前目标证据;历史 env 仅在证据缺失时兜底, + 证据存在时以证据为准。 +- 页面:生产保护与恢复面板加「恢复验证」块(完整 KB/索引/桶、状态、最近证据、失败原因、 + 管理员按钮、5s 轮询),作业类型补 verify 文案。 + +测试:make verify 16/16 全绿(go 1387 例 + web 454 例 + 契约/迁移/密钥/依赖锁定); +新增用例覆盖证据四条件、闸口证据优先、页面无证据/失败/通过三种呈现与管理员可见性。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/librarydrill/canary.go`、`cmd/librarydrill/main.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_release_handlers.go` 等 47 个 +统计:47 个文件,+2484/-335。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_role_test.go`、`cmd/worker/library_verify_test.go`、`internal/appconfig/library_role_test.go`、`internal/backuphost/runner_test.go` 等 10 个 + +## 开发快照 snapshot/v0.1.339 - 2026-11-02 +目标提交:4421dd38cec74fcfcdde177e65238a9ca38c42e5 + +### Fixed +- **fix(worker): 封存来源校验失败写入失败终态** +- **整库封存来源校验失败不再悬挂**:快照来源与配置不一致时,队列作业已 failed 而页面 + 一直显示"导出中"(实测 `libseal-lib-internal-20261102T234053`)。现在失败同时写入 + `library_job_stages` 与 `library_jobs`(failed + 原错 + "修配置后重新封存"),原错 + 不被遮蔽;来源一致的成功路径不变。 + +改动范围:`cmd/worker/library_source_candidate.go`、`cmd/worker/main.go`、`CHANGELOG.md`、`cmd/worker/library_seal_source_failure_test.go` +统计:4 个文件,+124/-1。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_seal_source_failure_test.go` + +## 开发快照 snapshot/v0.1.338 - 2026-11-02 +目标提交:22fb33aa7573f56903a588bf6ca27b9d01e8a006 + +### Tests +- **test(library): 两次recover的202响应留在raw里** +第一/第二次 recover 的 HTTP 202 与队列 id 原先只在用例断言里,raw 只留了失败结果。 +补两份 accepted 回应,报告可直接引用接口受理证据而不是转述断言。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go` +统计:1 个文件,+3/-0。 + +## 开发快照 snapshot/v0.1.337 - 2026-11-02 +目标提交:0d180aa401e388667b01475f497e5afc91396ccf + +### Tests +- **test(library): 台账行按列原文留档** +`to_jsonb` 行里有数字/布尔/NULL 列,直接解到 map[string]string 在真实数据上 +报 "cannot unmarshal number into Go value of type string"。改为解到 +json.RawMessage 再按列名存原文,比较的仍是每一列的精确值。 + +第二轮失败运行已按登记精确清理并读回。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go` +统计:1 个文件,+10/-4。 + +## 开发快照 snapshot/v0.1.336 - 2026-11-02 +目标提交:0a87dc8e037c4d7c61b8c566e9e9ca10e8b62308 + +### Tests +- **test(library): 修正D06台账读回的SQL别名** +`SELECT to_jsonb(x)` 缺表别名,真实跑出 SQLSTATE 42703,基线抓取在启动第一个 +新 PID 之前就中止。补 AS x,其余不变。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.335 - 2026-11-02 +目标提交:96472c1ddee829b58e7daf5f099a852c666b1b33 + +### Tests +- **test(library): D06场景同样激活恢复派发协议** +首轮 D06 跑出 worker 日志"restore marker dispatch protocol is not activated", +现场缺 05b-activation:激活条件只写了 ds01/ds01-n1,漏了新分支。补进 ds01Second, +否则场景停在激活前,acquire 永远到不了 durable dispatching。 + +首次失败运行按登记精确清理并读回(cleaned_readback)。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.334 - 2026-11-02 +目标提交:bad9034cf76e69c5b11009b469c5f5ca48a9433f + +### Tests +- **test(library): 增加D06第二次真实重启held场景** +N1 只证到"旧执行者被杀→一个新 PID 接管仍 held",没有证明第二个新 PID 再接管 +仍然 held。D06 要求同一 window/plan/effect 上连续两次真实换进程:第一次接管后 +真实终止那个新执行者、等真实租约过期、起第二个新 PID,再走同一 recover API。 + +新增 ds01-n1-second 场景复用 N1 的 dispatching+missing 边界,用同一份冻结基线 +(目标 PG 逐行、ES 命中代次、对象版本与逐字节摘要、effect 文件键值、marker 转发、 +派发台账整行、journal 状态)对两次接管分别做前后对拍;进程证据补 argv/scenario/ +可执行摘要,drill 构建标签一并落盘,避免把 4s 缩短租约当成生产默认。 + +仅测试夹具与证据,不改生产实现,不新增版本。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:2 个文件,+372/-33。 + +## 开发快照 snapshot/v0.1.333 - 2026-11-02 +目标提交:f330a75641cc1a69c1253458b079e69419709384 + +### Tests +- **test(library): 使用点复核同SHA错display** +激活点已覆盖同 SHA 错 display;使用点原先只构造了 unknown 与跨 SHA 篡改。 +补一条同 SHA、错 display 的台账篡改,证明使用点同样核运行 display,不只是比 SHA。 +仅测试夹具,不改生产实现。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/library_restore_dispatch_final_binding_test.go` +统计:1 个文件,+4/-0。 + +## 开发快照 snapshot/v0.1.332 - 2026-11-02 +目标提交:19ec03c4171e8700425c01d960d1ba5ae17c3c40 + +### Docs +- **docs: 说明跨SHA兼容默认关闭** +同名异域、副本、unknown 构建、同 display 不同不兼容 SHA 与错目标指纹。协议批次相等 + 只约束派发语义,不能证明某个 SHA 属于该批次;构建身份精确绑定运行完整 SHA,没有 + 可核受信批次成员证明的跨 SHA 兼容默认关闭(明确报 `cross-sha ... disabled`), + 不新建签名设施,也不引入第二份声明式 allowlist。 + +改动范围:`CHANGELOG.md` +统计:1 个文件,+4/-2。 + +## 开发快照 snapshot/v0.1.331 - 2026-11-02 +目标提交:6209072e43172c4d52c595b9330c6c7fbab2615a + +### Fixed +- **fix(library): 跨SHA兼容需受信证明默认拒绝** +原实现把 ProtocolEpoch 相等当作构建兼容证明:任何 40 位小写 hex SHA 加一个非空 +display 都会被记成 compatible_upgrade 并允许激活,换一个 SHA 即可绕开同 SHA 的 +display 绑定,构建身份仍是声明式。 + +收口为精确绑定运行完整 SHA:同 SHA 必须 display 相符,不同 SHA 一律失败关闭, +错误明确写"cross-sha compatible activation is disabled without a verifiable +protocol build membership proof"。不新建签名基础设施,也不引入第二份 allowlist; +使用点与激活点调用同一函数,行为一致。台账不重写、旧激活行保留(不匹配即 held)。 + +改动范围:`internal/libraryrelease/restore_dispatch_activation.go`、`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/library_restore_dispatch_activation.go` +统计:3 个文件,+18/-16。 + +## 开发快照 snapshot/v0.1.330 - 2026-11-02 +目标提交:e61f05c98cfd2cef067f3303965b543097d733eb + +### Tests +- **test(library): 跨SHA兼容升级保留为拒绝RED** +同协议批次下换一个合法 40 位 SHA 不能被 epoch 相等证明;激活与使用点都必须 +拒绝。当前实现仍会接受(compatible_upgrade=true),此提交先把期望钉在拒绝上, +作为修复前的 RED 证据;同 SHA 正例与同 SHA 错 display 拒绝一并保留。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/library_restore_dispatch_final_binding_test.go` +统计:1 个文件,+32/-10。 + +## 开发快照 snapshot/v0.1.329 - 2026-11-02 +目标提交:a9d5adf78f1213ca9f058588ae3f4ba4b040f41e + +### Fixed +- **fix(library): 使用点核对恢复目标指纹而非维护窗口指纹** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/platform/postgres/library_restore_dispatch.go` +统计:1 个文件,+10/-4。 + +## 开发快照 snapshot/v0.1.328 - 2026-11-02 +目标提交:e8f80694735dc12db4412a2c3f6724f390e87517 + +### Tests +- **test(library): 使用点区分维护窗口与恢复目标指纹** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/library_restore_dispatch_final_binding_test.go` +统计:1 个文件,+11/-4。 + +## 开发快照 snapshot/v0.1.327 - 2026-11-02 +目标提交:b7d6661a4e774721c05f3cb085fcb87e4658d15e + +### Docs +- **docs: 记录 D16 N2 真实 legacy 意图与缺失目标证据** +- **D16.N2 真实 legacy+missing 边界证据**:隔离场景在旧协议恢复标记 create 之前拒绝传输, + 现场由真实 `restore-mark-intent` 文件、确实缺失的目标文档与零 DS01 台账三件事共同构成; + 同一冻结二进制的新正式 worker 经 recover API 消费后 held,PG/对象/索引/effect 文件零变更、 + 台账不回填、入口 409。旧 N2 的 marker-present 事实与原始 raw 保留、不覆盖。 + +改动范围:`CHANGELOG.md` +统计:1 个文件,+7/-0。 + +## 开发快照 snapshot/v0.1.326 - 2026-11-02 +目标提交:97da6ceba86862703a6ed848d511161711bb5b88 + +### Tests +- **test(library): N2 缺失边界只允许 legit 意图且零 marker** +旧协议的标记 create 在传输层被拒,现场应只有 apply/restore 意图,没有 +claimed/finalize/restored;索引 doc 必须缺失,转发计数为零。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+21/-0。 + +## 开发快照 snapshot/v0.1.325 - 2026-11-02 +目标提交:7cdbb74c8fd75c76abeb480516781e8c98713ef5 + +### Tests +- **test(library): 补 D16 N2 真实 legacy 意图与缺失目标边界** +N2-missing 场景在旧协议回滚的恢复标记 create 之前拒绝传输,使边界成为 +"真实 mark-intent 已落盘、目标文档确实缺失、零 DS01 台账"。随后由同一冻结 +二进制的新正式 worker 走 recover API;观测 PG/对象/索引/effect 文件零变更、 +台账不回填、入口 409。旧 N2 的 marker-present 事实与 raw 不覆盖。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:2 个文件,+60/-10。 + +## 开发快照 snapshot/v0.1.324 - 2026-11-02 +目标提交:1aa60c159ebaac6c47799cd41bca6931ea3f85a1 + +### Tests +- **test(library): F4 正例按基线计数标记转发** +写入精确 marker 本身会经过 wire;断言应比较确认前后的增量,而不是绝对值。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/library_index_ds01_fixes_test.go` +统计:1 个文件,+4/-2。 + +## 开发快照 snapshot/v0.1.323 - 2026-11-02 +目标提交:245cefd6b0206827ba99ff88f92b206ef45cc335 + +### Fixed +- **fix(library): 收紧 DS01 派发租约/投递/治理身份与绑定冲突** +- **F2.a CAS 点实时租约**:`Acquire` 在取得 effect 行锁(最后一个可能阻塞的锁)之后、 + 状态 CAS 之前,按数据库 `clock_timestamp()` 重核 job/attempt/恢复投递租约;`Confirm` + 同样先锁 effect 行再核。等锁跨过到期一律零许可、状态保持 `prepared`。新增真 PG + 反例:等 effect 行锁期间租约过期。 +- **F2.b 恢复投递同事务绑定**:同一短事务内 `FOR UPDATE` 锁 queue 与 recovery operation, + 复核 queue id/type/state/payload 的 window+scope、idempotency key、当前 owner/attempt/ + lease,并按原 window 的不可变锚点重算 `LibraryRecoveryDigest`,与 operation 的 + `request_digest` 逐位比较;错 queue/window/digest/owner/过期/错备份/错 plan 全部零许可。 + 投递 owner 合成规则收敛到 `governance.LibraryDeliveryOwner` 一份。 +- **F3.identity 治理权威域与构建身份**:激活证据升至 v3,必须携带 PG 集群 + `system_identifier` + 库 OID、完整 40 位构建 SHA 与协议兼容批次;激活与每次使用点都 + 现场重读 `pg_control_system()`/`pg_database.oid`/`pg_is_in_recovery()` 并比对,拒绝 + 同名异域、副本、unknown 构建、同 display 不同不兼容 SHA 与错目标指纹。同一兼容批次 + 内的不同完整 SHA 记为 `compatible_upgrade`,不静默等同。 +- **F4.binding 绑定冲突类型化**:`RestoreDispatchStatus.BindingConflict` 与"仅禁止重发" + 的 `Held` 分开;准入、确认、既有意图续作与收尾在 prepared/dispatching/marker_confirmed/ + done 任一状态都拒绝绑定冲突;合法 dispatching + 精确 marker 仍可确认一次。 +- 真 PG/adapter 反例:effect 锁等待过期、恢复投递九类错配、错治理域/错批次/unknown、 + 四状态绑定冲突与合法 marker 正例。 + +改动范围:`cmd/worker/library_writer.go`、`internal/governance/library_recovery_identity.go`、`internal/libraryrelease/index_restore_dispatch.go`、`internal/libraryrelease/restore_dispatch_activation.go`、`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/library_restore_dispatch_activation.go` 等 16 个 +统计:16 个文件,+1126/-93。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_restore_dispatch_final_binding_test.go`、`internal/platform/postgres/library_restore_dispatch_linearization_test.go`、`internal/platform/postgres/library_restore_dispatch_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` 等 6 个 + +## 开发快照 snapshot/v0.1.322 - 2026-11-02 +目标提交:1e2bb385c77f3fe3b93152922e7666736a356936 + +### Tests +- **test(library): 激活时留档 journal 原始字节** +为什么:同一维护窗口在激活后还会继续写 journal(failed/recovered),事后无法从 +最终目录重算 activation 登记的摘要。激活当即把字节原样复制为 05b-journal-snapshot, +让"摘要 == 当时证据"可独立复核。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+9/-2。 + +## 开发快照 snapshot/v0.1.321 - 2026-11-02 +目标提交:bf7f5fa03c75752f237735ada53e808e3d19a397 + +### Tests +- **test(library): P1 用只读可用性投影核对恢复后业务准入** +为什么:POST /api/v1/library/restores 是"新建恢复作业"的入口,恢复后重复 POST +会制造第二次恢复;原任务的"同一受保护入口 409→200"应映射到零写副作用的 +GET /library/restore-availability:held 时报维护原因且 allowed=false,恢复后 +维护原因消失。不重复 POST,也不放宽权限。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+39/-2。 + +## 开发快照 snapshot/v0.1.320 - 2026-11-02 +目标提交:24608e132bcc5026d8a305e545c69347533a595c + +### Fixed +- **fix(library): 区分维护窗口指纹与恢复目标指纹** +为什么:维护窗口记录的是部署/实例指纹(journal 与 library_instance_maintenance), +observeRestoreTarget 返回的是内容/索引/桶身份;两者本来不同层(r38 实测 +dbe7… vs bb3f…)。v2 证据拆成 target_fingerprint 与 restore_target_fingerprint: +前者绑定窗口与治理行,后者绑定激活时的实时只读观察,避免用一个值冒充两层。 + +改动范围:`internal/libraryrelease/restore_dispatch_activation.go`、`internal/platform/postgres/library_restore_dispatch_activation.go`、`docs/architecture/ds01-activation-evidence.md`、`internal/platform/postgres/library_restore_dispatch_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go`、`internal/releaseadapter/v071/library_index_ds01_test.go` +统计:6 个文件,+33/-17。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_restore_dispatch_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go`、`internal/releaseadapter/v071/library_index_ds01_test.go` + +## 开发快照 snapshot/v0.1.319 - 2026-11-02 +目标提交:80254756f1e0ef604b448ab98d7fe02f8c0e80dd + +### Tests +- **test(library): adapter 激活夹具绑定维护窗口时间** +为什么:维护行由 attach 以 now() 建立,而 journal 的停写/排空观察早于它; +激活门正确拒绝"窗口在观察之后才开"。夹具把 acquired_at 校正到观察之前。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/library_index_ds01_test.go` +统计:1 个文件,+2/-1。 + +## 开发快照 snapshot/v0.1.318 - 2026-11-02 +目标提交:37f2dc73f92b1c2f8d826315ac19743ec640fae5 + +### Tests +- **test(library): 修正激活台账 JSONB 断言与 PG runner 工作目录** +为什么:jsonb::text 带空格,紧凑匹配才对;测试二进制必须在包目录下运行, +否则读不到 migrations/000078 的下迁移文件(这是 runner 的调用方式问题, +不是产品行为)。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/library_restore_dispatch_linearization_test.go` +统计:1 个文件,+3/-2。 + +## 开发快照 snapshot/v0.1.317 - 2026-11-02 +目标提交:9e31a6f95641b78c577a9aa4b70c5956ddef2afd + +### Fixed +- **fix(library): 激活台账沿用协议版本 1,夹具绑定维护窗口时间** +为什么:迁移 78 的 activation.protocol_version 约束是派发协议身份(=1), +证据文档 v2 只改变激活输入契约,不应触发 schema 变更;把两者混成一个数字会让 +"证据格式升级"变成一次不必要的 DDL。测试夹具补建/校正维护行(owner job、 +services、acquired_at 早于停写观察),否则真实校验会正确拒绝"窗口在排空之后才开"。 + +改动范围:`internal/platform/postgres/library_restore_dispatch.go`、`docs/architecture/ds01-activation-evidence.md`、`internal/platform/postgres/library_restore_dispatch_test.go` +统计:3 个文件,+27/-5。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_restore_dispatch_test.go` + +## 开发快照 snapshot/v0.1.316 - 2026-11-02 +目标提交:1c58f1ef7eb43c0848626d965420901906602af4 + +### Fixed +- **fix(library): DS01 激活消费真实维护 journal 证据** +- **激活不再接受调用方自填的停写声明**:协议升至 v2,证据只声明目标/实例/治理库/构建、 + 维护作业、journal 文件名与 sha256、旧 writer 集合;"已退出、已排空"由 Store 直接读取 + 维护窗口写下的 journal(入口 `drained` 且 `active_at_close=0`、后台 quiesce 全零并有 + 停写后复核、每个 writer 有 `stop_requested` 与退出码)与治理库 + `library_instance_maintenance` 行(同作业/实例/指纹/level、未释放、writer 集合一致) + 核出。任一不符不建 active 行。 +- **受信证据根缺失即关闭**:未配置 `LIBRARY_TARGET_JOURNAL_ROOT` + (`WithRestoreDispatchEvidence`)时返回 `ErrRestoreDispatchActivationDisabled`, + 不再退回"三个非空字符串 + 一个摘要就开门"。 +- **构建绑定**:证据 `protocol_build` 必须等于运行二进制的 `version.DisplayVersion()`; + 未注入构建身份时失败关闭。 +- 增加真 PG 反例:伪造/替换 journal 摘要、错 writer 集合、未退出、未排空、过期、 + 治理库/指纹/构建不符、窗口已释放、journal 已 recovered、未配置受信根。 +- 旧 `beforeCommit` 用例更名为"未提交回滚零许可",与 G1 的真实 COMMIT 回包丢失区分。 + +- 正式 worker 装配把 `LIBRARY_TARGET_JOURNAL_ROOT` 传给治理 Store,激活门只能在该根下 + 读 journal;`docs/architecture/ds01-activation-evidence.md` 记录逐项不变量与边界。 + +改动范围:`cmd/worker/main.go`、`internal/libraryfreeze/freeze.go`、`internal/libraryfreeze/journal_evidence.go`、`internal/libraryrelease/restore_dispatch_activation.go`、`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/library_restore_dispatch_activation.go` 等 16 个 +统计:16 个文件,+1101/-237。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_identity_integration_test.go`、`internal/platform/postgres/library_restore_dispatch_g1_test.go`、`internal/platform/postgres/library_restore_dispatch_linearization_test.go`、`internal/platform/postgres/library_restore_dispatch_test.go` 等 6 个 + +## 开发快照 snapshot/v0.1.315 - 2026-11-02 +目标提交:3b7ad9061c7068980971c7fc4a66cd3b03198a8f + +### Tests +- **test(library): 补 DS01 正式 worker 的 dispatching/legacy 负例** +新增两个隔离场景:N1 在 CAS 已提交、发送前终止旧 worker,新 worker 从 recover +路径必须零新写并保持 dispatching;N2 让旧协议只留下 mark-intent、无台账,协议开启 +后的新 worker 同样 held 且不回填。两者都核对 PG/索引/对象零增量与入口仍 409。 + +改动范围:`internal/releaseadapter/v071/library_index_restore_dispatch.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/formal_ds01_negative_r38_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:4 个文件,+263/-15。 + +## 开发快照 snapshot/v0.1.314 - 2026-11-02 +目标提交:882e38591d919886446b49523ad315678c6eddc6 + +### Fixed +- **fix(library): 恢复投递身份不进入 effect 摘要** +自动回滚先建立 prepared(没有 recovery queue),随后 recovery worker 用同一 +window/backup/plan 续作时带了新的投递身份;把它算进摘要会让合法续作被判成 +"绑定与不可变计划冲突"。owner/epoch 与 recovery queue/operation 属于执行锚点, +继续在同一事务里逐项核对,但不改变逻辑 effect 身份。 + +改动范围:`internal/libraryrelease/index_restore_dispatch.go`、`internal/libraryrelease/index_restore_dispatch_test.go` +统计:2 个文件,+35/-16。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/index_restore_dispatch_test.go` + +## 开发快照 snapshot/v0.1.313 - 2026-11-02 +目标提交:a1112d368a127ab73f706617200182ec47ed5e7a + +### Tests +- **test(library): DS01 重启旧 worker 时接受预期 kill 退出** +主动终止的进程 Wait 返回 signal: killed,不是失败;改为显式读回 /proc/ +不存在后,再用同一冻结二进制启动新 worker。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+4/-1。 + +## 开发快照 snapshot/v0.1.312 - 2026-11-02 +目标提交:ca32f2e661d06f9b2dd76b6f1ba4a6086dbf5364 + +### Tests +- **test(library): DS01 正式 worker 场景按零目标写断言** +D16 分支在派发 CAS 之前被拒,目标不该出现恢复标记;旧断言沿用 ES clean 分支, +期待 chunk-target 仍在。改为:DS01 held 边界要求 chunk-target 缺失、0 次 marker +create、0 次传输层拒绝,并在 recovery 后核对台账 marker_confirmed 且只转发一次。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+13/-1。 + +## 开发快照 snapshot/v0.1.311 - 2026-11-02 +目标提交:513aa9e6f5e87b916583fa1d3d418363f547dcfd + +### Fixed +- **fix(library): 收紧恢复派发真实绑定与同事务授权** +- **正式装配绑定**:`OpenProductionStack` 现在把 scope/实例与本次真实 attempt 身份传入目标; + 来源快照与写前镜像分别取自原 `WriteSet.TargetSnapshotID` 与本次 backup,不再把同一个 + `RestoreSnapshotID` 同时当来源和 before-image。缺锚点或二者相同在写之前失败关闭。 +- **派发线性化**:激活撤销与 `prepared→dispatching` CAS 在同一事务里用 activation 行锁串行化; + lease 在取锁之后用 `clock_timestamp()` 判定;同事务核对原 job/operation/attempt、来源/写集/ + 维护窗口与恢复投递(queue + recovery operation)绑定;同 effect 换 plan 摘要被拒绝。 +- **可核激活**:激活改为消费带 sha256 的证据文件(目标身份/指纹/实例、治理库名、协议构建、 + 逐 writer 的退出与排空观察、有效期);撤销不可回退为 `NULL`、activation 行不可删除, + down 在仅有 activation/撤销证据时也拒绝。 +- **准入/收尾完整分类**:绑定冲突、`prepared` 与 marker/claimed/finalize 矛盾在零目标写之前 + held;合法 `dispatching`+精确 marker 仍走确认分支。收尾改用原 window 权威台账全集(跨 plan), + 拒绝计划外记录、换 plan 别名与缺 `restored` 证据的 effect。 +- **可恢复区段**:`prepared` 已提交但 mark-intent 未落盘时,核对绑定一致且无矛盾后补写同一 + 意图并继续;无台账的旧意图仍 held,r33 不回填。 + +- 绑定冻结实际目标实例/指纹/索引 UUID,同名重建的目标或索引会让旧证明失效;它们用于识别 + 对象,不用于证明历史未发送。协议仍不声称 ES 端 fence 或旧 `dispatching` owner 零迟到写。 + +改动范围:`internal/libraryrelease/index_restore_dispatch.go`、`internal/libraryrelease/restore_dispatch_activation.go`、`internal/librarystack/stack.go`、`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/migrations/000078_library_restore_dispatch.down.sql`、`internal/platform/postgres/migrations/000078_library_restore_dispatch.up.sql` 等 22 个 +统计:22 个文件,+2199/-227。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/index_restore_dispatch_test.go`、`internal/librarystack/stack_test.go`、`internal/platform/postgres/library_restore_dispatch_g1_test.go`、`internal/platform/postgres/library_restore_dispatch_linearization_test.go` 等 8 个 + +## 开发快照 snapshot/v0.1.310 - 2026-11-02 +目标提交:3bce6c7a3fb1b870c4dcbca554b1ba75f464e5f9 + +### Fixed +- **fix(library): 撤销派发激活后不得用旧证据重开** +为什么:撤销若只是把 revoked_at 置空,等于"撤销"变成界面动作; +部署想重开必须给出新的排空证据或换目标身份,不能复用同一条。 + +验证:真 PG 用例先绿,再把 SQL 变异回 DO UPDATE ⇒ 用例红于 +"revoked activation was reopened with the same evidence",还原后再绿。 + +改动范围:`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/library_restore_dispatch_test.go` +统计:2 个文件,+38/-6。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_restore_dispatch_test.go` + +## 开发快照 snapshot/v0.1.309 - 2026-11-02 +目标提交:18a4b83f7844589fced0d7d86b5897b2d945e3b7 + +### Tests +- **test(library): 派发用例输出台账状态与回执证据** +用例本身要能在 raw 里留下"转发次数 + 台账 state/owner/nonce/绑定/回执", +否则审核只能看到 PASS 字样,无法核对状态链与许可归属。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/library_index_ds01_test.go` +统计:1 个文件,+17/-0。 + +## 开发快照 snapshot/v0.1.308 - 2026-11-02 +目标提交:7b2ab642f7788466c8bb0cdf662c9c49825483cf + +### Docs +- **docs(changelog): 记录 DS01 恢复派发开发快照** +- 治理库新增 `library_index_restore_dispatch` 与 `library_index_restore_dispatch_activation`(迁移 78): + 恢复标记 create 的"是否已有人取得发送许可"成为单调持久事实(`prepared` → `dispatching` → + `marker_confirmed`),唯一 key 绑定目标/原作业/原计划/索引/文档,绑定与状态由触发器拒绝改写与回退, + down 迁移在已有记录时拒绝执行。 +- 新增 `LIBRARY_RESTORE_DISPATCH_V2`(默认关闭)。开关只表示"允许装配";真正发放许可还要求治理库 + 对该目标存在未撤销的激活登记(`library_index_restore_dispatch_activation`),配置 true 不是独占证明。 + +- 删除型恢复(`Before` 非空、`After` 为空,恢复写入是独占 `_create`)只从唯一 dispatcher 取得一次性 + 许可:独立短事务按 job→operation→active attempt→effect 取锁并 CAS,**COMMIT 明确成功后才发送**; + 发送请求关闭传输层重放(`GetBody=nil`),失败不自动重试。 +- `Restore` 在任何 PG/对象/索引写之前做纯读未决准入:旧 `restore-mark-intent` 无台账、 + `dispatching` 且目标无精确 marker 一律 held;收尾前再拒绝一次未决效果,避免"先写一半再报 held"。 +- 自动 rollback 与维护恢复共用同一目标装配与准入(baseline 永不获得该许可)。 + +- 只承认新协议 `prepared` 区段的跨进程续作;不回填历史 intent、不把 `missing`/`stats`/内存计数当 + 未派发证明,不触碰 r33。协议不声称解决 ES 端 fencing,也不声称接管后旧 owner 零迟到写。 + +改动范围:`CHANGELOG.md`、`docs/architecture/ds01-restore-dispatch.md` +统计:2 个文件,+96/-0。 + +## 开发快照 snapshot/v0.1.307 - 2026-11-02 +目标提交:0a7c9f85105657ed91e9bf0a156952dfc8d36947 + +### Changed +- **feat(worker): 接线恢复派发台账并按激活登记启用** +为什么:派发协议只在 worker 里装配却没人接,就等于只写了 store +mock。同步失败的自动 rollback 与维护恢复都必须拿到同一份台账; +baseline 只读装配永远不传,避免建基线顺带获得恢复写许可。 + +开关 LIBRARY_RESTORE_DISPATCH_V2 默认关闭,且只是"允许装配": +治理库没有该目标的未撤销激活登记时,Prepare/Acquire 直接 fail closed。 + +验证:cmd/worker 单测覆盖默认关闭/显式开启/无台账三态; +v071 真 ES 用例里自动 rollback 与手动恢复走的是同一个 Restore。 + +改动范围:`cmd/libraryresidue/main.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go`、`cmd/worker/library_wiring.go`、`internal/appconfig/config.go`、`internal/librarystack/stack.go` 等 7 个 +统计:7 个文件,+80/-16。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_restore_dispatch_wiring_test.go` + +## 开发快照 snapshot/v0.1.306 - 2026-11-02 +目标提交:e09fddcb74b49cea56256106e632c29b404f6446 + +### Changed +- **feat(library): 删除型恢复按台账准入并唯一发送** +为什么:恢复在发现 index held 之前可能已经回放 database/resources; +"最后数据相同"不等于"本次零派发"。改为 Restore 在任何写之前按整份 +冻结计划做纯读准入,收尾前再拒绝未决 effect。 + +删除型恢复的 _create 只从唯一 dispatcher 发出:preflight/故障钩子 +在 CAS 之前,许可来自 COMMIT 明确的短事务;发送请求 GetBody=nil, +传输层无法重放。旧意图无台账、dispatching 无精确 marker 一律 held。 + +验证:真 ES+真 PG 覆盖旧意图零写 held、prepared 续作到终态 +(create 恰 1 次)、dispatching+missing 两轮零写、新 OS 进程续作正例。 + +改动范围:`internal/releaseadapter/v071/library_index_effects.go`、`internal/releaseadapter/v071/library_index_restore_claim.go`、`internal/releaseadapter/v071/library_index_restore_dispatch.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/search.go`、`internal/releaseadapter/v071/data_admission_r10_test.go` 等 7 个 +统计:7 个文件,+945/-35。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/data_admission_r10_test.go`、`internal/releaseadapter/v071/library_index_ds01_test.go` + +## 开发快照 snapshot/v0.1.305 - 2026-11-02 +目标提交:fad26166a466de2d3533523e4fad69f93af01bc2 + +### Changed +- **feat(library): 新增恢复标记派发台账与一次性许可** +为什么:删除型恢复的独占 create 在 ES 侧没有"从未写过"的持久证据, +missing/stats/内存计数都不能当未派发证明。改成为每个固定 effect 维护 +单调台账:prepared -> dispatching -> marker_confirmed,绑定换不了、 +状态回退不了、行删不掉。 + +许可只在独立短事务里 CAS 并 COMMIT 明确成功后返回,消费一次即作废; +提交结果不明时不发许可。激活登记是配置之外的第二个条件。 + +验证:真实 PG(隔离 schema)覆盖并发唯一赢家、旧 epoch 拒绝、 +单调/绑定/删行拒绝、提交不明零许可、无激活 fail closed、down 拒绝抹证据。 + +改动范围:`internal/libraryrelease/index_restore_dispatch.go`、`internal/platform/postgres/library_restore_dispatch.go`、`internal/platform/postgres/migrations/000078_library_restore_dispatch.down.sql`、`internal/platform/postgres/migrations/000078_library_restore_dispatch.up.sql`、`internal/libraryrelease/index_restore_dispatch_test.go`、`internal/platform/postgres/library_restore_dispatch_test.go` 等 7 个 +统计:7 个文件,+1060/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/index_restore_dispatch_test.go`、`internal/platform/postgres/library_restore_dispatch_test.go`、`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.304 - 2026-11-02 +目标提交:47be4ae502b728222b219aeb1d630ad273690eb5 + +### Fixed +- **fix(web): 未读到目标观测不再误报非空并标注旧观测** +- 发布管理:还没有读到生产目标状态时(首次加载、整库状态读取失败、当前范围没有数据) + 不再把本地默认值当成"目标非空",也不再提供会返回 409/503 的"建立目标权威基线"入口; + 有效四类响应(有权威版本 / 已读且为空 / 非空无版本 / 不可读)的呈现与入口保持不变。 +- 发布管理:刷新失败但仍保留上一次目标观测时,页面标明"上次观测 · 刷新失败,可能已过期", + 不把旧结果当成刚刚验证过的状态。 + +改动范围:`web/package-lock.json`、`web/package.json`、`web/src/ReleaseTargets.mount.test.tsx`、`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`CHANGELOG.md` +统计:6 个文件,+892/-24。 + +## 开发快照 snapshot/v0.1.303 - 2026-11-02 +目标提交:6827fa922fc688c2af43bde0baef2ce0bc34fad2 + +### Fixed +- **fix(candidate): 夹具用产品探针证空并复用真实索引 mapping** +首轮试跑暴露三处夹具缺陷(都在 test-only 文件里,产品代码未动): +- 目标骨架只核 knowledge_bases:最小夹具 schema 没有 tenants 表,改为按 KB 行数与 + 产品 ScopeContentEmpty 的十表计数证空; +- 运行期索引曾把 GET _mapping 的结果再包一层 mappings,ES 400; + 现在直接回放原 mapping 文档(properties 层级与封存时完全一致); +- 目标指纹改在夹具分配桶名之后用同一份 env 计算,运行器再把桶名写进 API/worker env。 + +试跑两次均按 cleanup_empty_target_ui.py 精确清理并读回(resources 全不存在)。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/candidate_ui_fixture_test.go` +统计:1 个文件,+6/-9。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/candidate_ui_fixture_test.go` + +## 开发快照 snapshot/v0.1.302 - 2026-11-02 +目标提交:c31a7df4cae151daa6515f0e754f388c7764eebb + +### Changed +- **style(test-scope): 可选范围只追加两条,恢复原有紧凑格式** +上一笔把 scripts/test-scope.json 整体重排成缩进格式,diff 变成 196 行噪音。 +这里恢复文件原本的一行一条格式,只保留新增的 TestCandidateEmptyTargetFixture / +TestCandidateAttestation 两条;内容(112 条可选)未变。 + +改动范围:`scripts/test-scope.json` +统计:1 个文件,+33/-165。 + +## 开发快照 snapshot/v0.1.301 - 2026-11-02 +目标提交:770252b55aef80f777820e37bd9944a875858ddf + +### Tests +- **test(candidate): 空目标候选夹具与网关审计续签** +父要在真实浏览器里点"签发授权 → 启动同步",需要一个目标确实为空、来源确实存在 +的候选环境。两个 test-only 入口(默认 skip,已登记 scripts/test-scope.json 的可选范围): + +- TestCandidateEmptyTargetFixture:用已有 DS02/r38 夹具装配十表全非空的独立 fullshape + 测试来源并封存,登记为 test_sealed;同时用产品自己的 ScopeContentEmpty 证明目标范围 + 为空(KB/tenant 骨架在、十表内容 0 行),并另建同 mapping 的运行期索引(夹具索引会在 + 测试结束时删除)。目标指纹用夹具真实桶名重算,保证与 API/worker 同一份 env 一致。 +- TestCandidateAttestation:签发/续签 object-gateway 审计(有效期上限 1 小时), + 四个证据摘要绑真实文件(写入者清单、只读策略、reader 拒写探测、真实排空记录)。 + +不改产品代码、不放宽任何准入;来源明确标 fixture-assembled-legal-fullshape, +不冒称原 C01 两份封存或 Capture/Export HTTP。 + +改动范围:`scripts/test-scope.json`、`internal/releaseadapter/v071/candidate_ui_fixture_test.go` +统计:2 个文件,+534/-31。 + +## 开发快照 snapshot/v0.1.300 - 2026-11-02 +目标提交:c256f2985cadf1ce7617bbc86312b9a8e21e79b0 + +### Fixed +- **fix(web): 空/非空/不可读目标分开呈现并收窄建基线入口** +后端 overview 早已区分 known / unknown+empty_baseline / unknown 非空 / unreachable, +页面只判断 state === 'known',把"已证空"和"读不到"都写成"未知",并在两种情况下 +都给出"建立目标权威基线"入口——而该接口对空目标返回 409 +library_baseline_target_empty、对读不到的目标返回 503 library_baseline_unprobeable。 +按后端真实契约做最小分类: + +- LibraryKnown 补 empty_baseline 与 unreachable 两个已存在的字段/状态; +- 页面按四类分别呈现:known 显示生效版本+快照摘要;空目标写明"已读且为空/首次发布/ + 写前仍生成完整预备份";非空缺权威版本才指向基线入口;不可读照实说读取失败; +- baselineNeeded 从"非 known"收窄为"非空且无权威版本",不动授权、写闸、预备份、 + 容量与 worker 执行准入等任何后端检查,也不放宽后端的 409。 + +行为测试新增四类状态、空→不可读→空刷新不残留旧状态、空目标无候选时按钮禁用; +原"非 known 一律未知"的断言随文案更正。本地门禁 16 项 success +(tests-web 498 passed,tsc -b 无错)。 + +改动范围:`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts` +统计:3 个文件,+175/-11。 + +## 开发快照 snapshot/v0.1.299 - 2026-11-02 +目标提交:f1cddfb60117dada470af05f0b400c3e78984ee9 + +### Tests +- **test(r39): 重放前后逐对象实取字节摘要比对** +原重放段只比 Listing 的 key@versionId#etag#size,同一版本号下的字节 +被改写它看不见,因此"重放后字节不变"缺实测证据。 + +改为重放前后各调一次 r38ObjectVersionDigests:逐 key+versionId 真实 +GET 并本地算 SHA256,落完整版本清单(含版本、ETag、size、摘要), +再逐项比较;GET 失败、清单缺条目、版本增减或摘要不同都判失败。 +治理 PG/ES/ledger/receipt 零新增断言保持原样。 + +离线负控 TestR39ReplayObjectByteControl:固定"旧判据对字节改写报相同" +的盲区,并要求新判据在同一元数据上报 bytes changed、正例不误报、 +空清单/缺摘要必须红;用变异(去掉摘要比较)确认该负控会红后恢复。 + +台账:04-baseline-and-auth.json 分开记录授权 201 与基线 202, +不再让后一次状态码覆盖前一次。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_publish_http_r39_helpers_test.go`、`internal/releaseadapter/v071/formal_publish_http_r39_replay_bytes_control_test.go`、`internal/releaseadapter/v071/formal_publish_http_r39_test.go` +统计:3 个文件,+176/-8。 + +## 开发快照 snapshot/v0.1.298 - 2026-11-02 +目标提交:d2ff089b9554708dff7c473d3f25246920fadac0 + +### Changed +- **ci(checkout): 补取事件 before 失败时重试一次并保留错误** +浅克隆里用 fetch --depth 取一个未公开的对象(改写推送后的旧提交就是这种)时, +git 会在这次 fetch 中途重写 .git/shallow,随后中止: +fatal: shallow file has changed since we read it。 +before 取不到 ⇒ commits 检查算不出范围 ⇒ 整条流水线红,红的原因与代码质量无关。 + +证据(真实 CI):run 305–317 共 13 次(含 317 的原地重跑)全部停在这一步; +同一仓库里紧接着重试同一条命令即成功。用同一 runner 镜像在容器里跑 +"bootstrap + ci_checkout.sh --post-checkout"(真实事件负载)复现出该 fatal, +原脚本报"取不到事件 before",改后报"已补取事件 before eaf389e506f5", +随后 ci_gate 算出 事件 Git 范围 eaf389e..12b3769|1 条提交 并通过。 + +只加一次重试与错误回显:范围选择、判红口径、fail-closed 语义都没动 —— +两次都失败仍明确报警,由 commits 检查判红,不收缩范围、不放行。 + +改动范围:`.gitea/scripts/ci_checkout.sh` +统计:1 个文件,+17/-3。 + +## 开发快照 snapshot/v0.1.297 - 2026-11-02 +目标提交:12b37690ec95afef6c7fd4a6e575a13f4104bd52 + +### Tests +- **test(r39): 真实HTTP发布成功闭环与同请求幂等** +- 新增单一场景用例 `TestFormalPublishHTTPReplaySucceededR39`(`KG_R38_SCENARIO=publish-http`): + 发布作业只来自真实监听的 `POST /api/v1/library/syncs`(管理员会话 + 真实授权), + 不调用 `store.CreateLibraryDispatch` 冒充 HTTP,也不再走 httptest。 +- 新增 `r39` 证据链:来源身份先声明后封存(独立推导 `source_id`,来源行真实落进隔离 + PostgreSQL schema 再读回)、目标基线走正式 `POST /api/v1/library/baselines`(真实整库 + 捕获 + 校验 + `target_baseline` 证据,不手写生效版本)、handoff 用真实 `local` transport + 与真实 `libraryctl` 的 `snapshot-pack/unpack/verify`、发布后逐表/引用/索引向量/对象版本 + GET SHA 的独立 oracle,以及对同一 body 原样重放(`reused=true`,治理行/目标内容/索引/ + 对象版本逐字节不变)。 +- 新增外置运行器 `publish_http_runner.py`:沿用已审 `r38_runner.py` 的"创建前登记 + + 创建后补不可变 ID"与四次源码门禁,清理仍交给已审 `r38_cleanup.py`(run 目录名以 + `r38-` 开头),不重跑旧 runner、不改它的证据。 + +- 本批次只新增一个单场景测试与外置 wrapper,旧 r38 故障恢复分支(含 `:775` 的测试直建 + dispatch)一行未改,仍按原语义保留。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_publish_http_r39_helpers_test.go`、`internal/releaseadapter/v071/formal_publish_http_r39_test.go` +统计:3 个文件,+1608/-0。 + +## 开发快照 snapshot/v0.1.296 - 2026-11-01 +目标提交:a1472ff29fad05013819640b53bca42c214b455f + +### Fixed +- **fix(ds03): 早段held断言按发布代次比较整份索引** +首轮到达held时按chunk-target取文档失败:早段数据库阶段失败前索引仍是 +发布写入的源码分块,before-image的chunk-target本就不该出现。改为整份 +读回等于applied,并显式断言chunk-target未提前回来。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+25/-22。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` + +## 开发快照 snapshot/v0.1.295 - 2026-11-01 +目标提交:f076c3af9cbba8a23f6e8f9749e926ea963fd118 + +### Fixed +- **fix(ds03): 负控行复用声明形状以满足非空列** +首轮真实早段运行在插入 pending=1 负控行时被 knowledges 的 NOT NULL +约束拒绝(enable_status),说明控制行缺少声明形状。改为复制本轮 +canonical 来源行再覆盖 id/范围/pending,负控仍由正式结算拒绝。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_early_test.go` +统计:1 个文件,+11/-3。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_early_test.go` + +## 开发快照 snapshot/v0.1.294 - 2026-11-01 +目标提交:59eb12296a7fa4abdc9ec149426c72ee68a9e779 + +### Docs +- **docs(ds03): 记录早段PG恢复与工具登记/读回修复** +- 新增早段 database 恢复失败用例 `TestFormalMaintenanceRecoveryPGEarlyRestoreHeldThenRecovered` + (`KG_R38_SCENARIO=pg-early`):仍然复用 `after:wiki` 发布校验失败入口,唯一新增故障是恢复 + **第一段 database** 的真实主键冲突——发布整库替换已删掉 before-image 目标文档,先用一行 + "外部范围、同主键"的 `knowledges` 占住该 id,恢复自己那条 `INSERT` 得到服务器 + `SQLSTATE 23505`。失败文案必须同时含 `恢复阶段 database 失败` 与 `SQLSTATE 23505`, + 且不得出现 wiki 阶段或 ES/对象文案。 +- 新增本轮专用来源副本 `r38LegalSourceSpec`:只把未删除来源文档改成 + `parse_status=completed`、`pending_subtasks_count=0`,共享 `ds02SourceRows` 一行不改。 + 封版前用产品自己的 `checkLibrarySourceSettlement` 在真实 PostgreSQL 上结算这份声明 + (隔离 schema,避免来源 id 与发布写入共用主键),并保留原 pending=1 被正式契约拒绝的负控; + 负控行按 id+租户+库精确删除并读回 0。 +- 新增早段 held 证据 `r38PGEarlyFaultHeld`:故障行仍在;同一 `INSERT` 复测仍被服务器以 + 23505 拒绝且没有多出行;database 阶段整段回滚,所以十张表都还是发布写入后的内容 + (与 applied 逐表多重集相等,且至少一张表不同于 before-image);索引仍是发布那一代, + 既没有恢复 marker 也没有 restored 回执。 + +- `r38_runner.py` 的隔离资源改为**先登记再创建**:network/DB/container 在创建调用之前把 + 名字与归属写进 `state.resources`,创建成功后补不可变 ID(DB 补 oid),创建响应丢失时 + 保留 `create-unknown` 清单;登记保存失败直接中止,不再先创建后登记。 +- `r38_cleanup.py` 的网络读回只接受 daemon 明确的 not-found(`no such network`/`not found`): + rm/inspect 的连接、认证、解析错误一律落 `cleanup-incomplete` 且非零,绝不当作"不存在"; + name/label 必须与本轮登记一致,否则零删除。创建响应丢失的 container/DB 按登记名 + + 项目 label 重新证明归属,归属不明失败关闭、不猜删。 + +- `r38_runner.py` 的末次(finally)provenance 快照此前只记录不比较:现在 HEAD/tree_digest/ + 干净状态任一项变化都会让整体非零,状态保留原测试结果并附加 + `source-gate-failed-after-final`,精确清理照走,不再出现只有最后一次采集变化却 + `passed-cleaned`。 +- 离线控件补齐对应精确反例(末次 tree/HEAD/dirty、网络 daemon 错误/rm 失败/label 不符/ + 未登记名、创建响应丢失的 container/DB、登记保存失败),期望条数由程序统计, + 原 10+7 案例与旧失败证据保持不变。 + +--- + +改动范围:`CHANGELOG.md` +统计:1 个文件,+42/-0。 + +## 开发快照 snapshot/v0.1.293 - 2026-11-01 +目标提交:6ec18212948f368a266d3274bcf2f203535805bb + +### Tests +- **test(ds03): 新增合法来源早段PG恢复用例** +复用一个已证明的 after:wiki 发布失败入口,唯一新增故障是恢复第一段 database +的真实主键冲突(外部范围同主键 knowledges 行,SQLSTATE 23505)。来源输入改用 +R38 专用副本(未删除文档 completed/pending=0),封版前用产品自己的 +checkLibrarySourceSettlement 在真实 PG 上结算,并保留原 pending=1 被正式契约 +拒绝的负控;共享 ds02SourceRows 与 quiesce 都不改。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_early_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:2 个文件,+435/-14。 + +## 开发快照 snapshot/v0.1.292 - 2026-11-01 +目标提交:099e58b5dc902e7f463a8404d3aa864481547c27 + +### Docs +- **docs(ds03): 修正PG用例变更记录与故障机制** +`after:wiki` 定点进入回滚,唯一新增故障是真实 PG 错误——恢复最后一段 PG 写入(wiki 阶段) + 会把 before-image 的日志行按原 id 插回目标范围;先用一行"外部范围、同主键"的日志占住该 id, + 那条真实 `INSERT` 得到服务器 `SQLSTATE 23505`。用例记录 SQLSTATE、同一语句的探针读回 + (没有多出行)与失败阶段事务回滚;`恢复阶段 wiki 失败` 与 `SQLSTATE 23505` 必须同时在失败文案里。 +- 新增 held 现场证据:故障行未被自动解除;held 是真实的部分恢复状态——database 阶段已提交、 + 四张 wiki 表仍是发布写入、共享 `resources` 的新增行按设计留到四阶段全部成功后再清, + 逐表与 applied/before-image 比对并断言失败阶段是整段回滚而不是半写。 +- 新增同窗口续作证据:解除故障只删除那行外来日志;同一 `window_job_id` 的 + +改动范围:`CHANGELOG.md` +统计:1 个文件,+8/-7。 + +## 开发快照 snapshot/v0.1.291 - 2026-11-01 +目标提交:e760d85427ff733726a591778d97e6fdbeae4dc6 + +### Tests +- **test(ds03): 新增PG恢复失败后同窗口续作用例** +- 新增 PG 恢复失败用例 `TestFormalMaintenanceRecoveryPGRestoreHeldThenRecovered`:复用已证明的 + `after:wiki` 定点进入回滚,唯一新增故障来自本轮一次性业务库里真实的 PG 错误——恢复事务写 + `knowledges` 时触发器 `RAISE EXCEPTION ... USING ERRCODE='42501'`。用例记录触发器 DDL、 + 服务器返回的 SQLSTATE、探针语句与读回(该语句没有留下任何行),并断言失败事务整体回滚后 + 目标仍是发布写入后的内容;`恢复阶段 database 失败` 与 `SQLSTATE 42501` 必须同时出现在失败文案里。 +- 新增 held 现场证据:故障未被自动解除(开关仍开、触发器仍在),目标行逐表等于 `after:wiki` + 处的 applied 读回,索引仍是发布写入后的内容(恢复没有走到索引阶段)。 +- 新增同窗口续作证据:解除故障只 DROP 本轮触发器/函数/开关表;同一 `window_job_id` 的 + `POST /api/v1/library/maintenance/recover` 完成后复核十表、引用承诺、对象版本字节、索引 + 与网关回执/投递,并保存 replay 原始读回。 + +- 隔离夹具在 `reset` 时消费运行器登记的唯一索引(`KG_R38_INDEX`),并在第一次写入前把实际 + namespace 落到本轮目录;清理器核对「运行器登记 / 夹具落盘 / 实际清理」是同一个名字, + 登记不一致时拒绝删除,不再只依赖 `t.Cleanup`。 + +- 证据目录的运行器:构建后/执行后比较 HEAD、干净状态与源码树摘要,构建后不符直接拒跑用例、 + 执行后不符结论作废(不再出现源码已改却 `passed-cleaned`);finally 里的 provenance 异常 + 不再跳过精确清理。 +- 证据目录的清理器:PG 环境发现等前置动作失败也落 `resources-cleaned.json` 与非零终态; + 已有的 `cleanup-incomplete` 结果文件不再因"文件存在"而返回 0。 + +--- + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go`、`internal/releaseadapter/v071/library_writeset_integration_test.go` +统计:3 个文件,+307/-27。 + +## 开发快照 snapshot/v0.1.290 - 2026-11-01 +目标提交:98e7279632227aea8ce2281db5843264ccb6d423 + +### Tests +- **test(ds03): 新增对象版本逐条绑定移除回执** +把"发布真实新增的版本在恢复后消失"从"桶里没有这个 key"加强到:每个新增 +versionId 都必须有同 key + 同 versionId 的网关 removed 回执,并断言绑定条数>0; +回执/投递的 plan 仍必须等于发布期写集合摘要。 + +验证:go vet 通过;make verify 在本提交前的同一源码状态已 16 项全 success, +最终证据用提交后的完整 SHA 重跑。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+28/-1。 + +## 开发快照 snapshot/v0.1.289 - 2026-11-01 +目标提交:30f223bcc4ef19aea5622468c2baf39d08e5b6c7 + +### Tests +- **test(ds03): 对象恢复补十表全形状与版本字节指纹** +- 对象恢复分支的 before-image 与来源快照都改成十表全非空:直接复用 DS02 已验的十表声明 + (`ds02SourceRows`/`ds02Expected`/本地号映射 oracle),目标自己的标签、标签关系、Wiki 目录、 + 页面问题、Wiki 日志不再是空表;同租户邻库与外部租户行作为哨兵逐表非空。 +- 新增逐种引用承诺计数:父子分块、标签关系、资源绑定、目录父子、Wiki 目录/来源/分块引用、 + 页面问题与日志引用,在声明来源、发布实际写入、恢复后 before-image 三处都要求 `matched>0`。 +- 新增对象版本字节指纹:对 before-image 每个受保护 `versionId` 单独 `GET` 计算 SHA256,恢复后 + 同 `version` 再独立计算一次并逐条比较;ETag/size 只作旁证,不再充当"逐字节相同"的证据。 +- 新增发布期/恢复期映射对应证据:发布期写集合摘要同时出现在预备份版本证据、网关回执与投递 + 计划里;恢复意图的 `request_digest` 由窗口/作业/目标/来源/备份/该摘要独立重算后逐字符比较。 +- 新增重放后的原始读回:PG 十表、索引、对象版本清单与版本字节、网关回执/投递都落盘,供父 + 独立比对,而不是只记录一条 PASS。 + +- 隔离运行器把主命令、label、完整 SHA、版本号、二进制摘要与测试名写进同一份 `run-identity.json`, + 报告头与 raw 统一取自该文件,避免"头写旧运行、身份是新运行"。 +- 运行器与清理器的 label 前缀统一为 `ds03-object-*`(对象分支),清理仍只清本轮登记资源并 + 只使用已核对的不可变容器 Id。 + +- 对象恢复用例的对象分支改用真实 v071 十表 catalog 建表,目标行本地号放在序列起点之前,并让 + 目标旧文档预置目标知识库绑定的 embedding 模型,使"恢复回放"回到的写前状态是真实形状。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_fullshape_test.go`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:3 个文件,+911/-22。 + +## 开发快照 snapshot/v0.1.288 - 2026-11-01 +目标提交:2cbeca8d1d865d3b2b0b0d1e6c04567991a394f8 + +### Tests +- **test(ds03): 记录对象拒绝的网关原始响应** +拒绝事件补 503 与原始正文,证据可直接引用网关响应,而不只依赖客户端错误文本。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+3/-1。 + +## 开发快照 snapshot/v0.1.287 - 2026-11-01 +目标提交:b39902ef9060b955436d47ae9c705efe0cf81abf + +### Tests +- **test(ds03): 修正对象效果文件字段读取** +对象效果 JSON 使用 resource_id/key 小写标签;按 Key 匹配会得到空集合, +导致已发生的 delete-intent 被误报为缺失。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.286 - 2026-11-01 +目标提交:68f03bb30e3bd1b2d2bf812583904691b6a85e7b + +### Tests +- **test(ds03): 对象恢复传输失败后同窗口恢复** +- 新增对象恢复失败分支的真实隔离用例 `TestFormalMaintenanceRecoveryObjectRestoreHeldThenRecovered`: + 在对象网关前拒绝一次"回滚删除本轮创建对象"的请求(派发前 503,转发计数 0,不产生网关 + intent/receipt),让自动回滚真实失败并进入窗口 held + scope/instance `needs_restore`; + 撤障后仍用同一 `POST /api/v1/library/maintenance/recover`、同一 window/job/backup/plan/mapping + 完成恢复,并逐项读回 PG 十表整行、ES 完整 `_source`、对象全部分页版本/delete markers 与字节摘要。 +- 新增四类进程身份证据:api / worker / object-gateway / 测试进程分别绑定最终源码树、binary + sha256、`/proc//exe` 实际 executable 与 commit;测试进程自证 executable 摘要,不匹配即失败。 +- 新增对象网关拒绝现场:被拒请求的 operation/key/action/plan/epoch、目标 receiver、转发计数、 + 网关侧 intent/receipt 行数、引用到的 durable create receipt,以及对象恢复的 `delete-intent` 效果。 + +- r38 对象分支不再把旧的 ES clean-dispatch 503 改名成对象故障:ES 回滚写入必须真正完成, + 失败必须来自对象恢复边界;未走正式发布 API 的入口在证据中明确标注"发布 API 入口未证"。 +- 对象清单改为分页枚举全部 versions 与 delete markers,版本指纹包含 ETag 与字节数。 + +- 修正隔离运行器工具(证据根,不改变产品版本):对象删除前先核对容器 name/Id/label,之后 + 破坏性操作只用不可变 Id;清理异常(含 PG 读回 RuntimeError)统一落 `cleanup-incomplete` + 失败终态与清理结果文件并非零退出,不再持久 `passed`。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:2 个文件,+423/-23。 + +## 开发快照 snapshot/v0.1.285 - 2026-11-01 +目标提交:93c0f34aac02af0341dc7d9dfb35f669a21c6dd3 + +### Tests +- **test(ds02): 同租户不同知识库十表同输入对照** +该跨租户 bundle 是**受约束回归对照**:6fe98c9 在 wiki 之前无法跨租户搬运资源,旧侧归档 + 只能省略 `resources` / `resource_bindings` 两表,因此不能据此声称"同输入已收口"。 +- 新增十表同输入对照(同租户、不同知识库):`ds02-identical-input-bundle.json` 两侧各自 + 规范化并封存完整十表来源归档(含 `resources` / `resource_bindings`),旧侧 `6fe98c9` 用 + 真实 PG/ES/MinIO 对象适配复现 `wiki_log_entries_pkey / SQLSTATE 23505`,新侧同输入 + `succeeded` 并通过映射/内容 oracle。两侧在执行前各自动导出逐表来源归档、实际目标种子、 + 完整 DDL、三条序列、ES 文档与对象字节;唯一 namespace 替换(对象桶名 `%BUCKET%`) + 按同一规范归一后语义输入 hash 相同,物理桶名单独记录。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/ds02_identical_input_test.go` +统计:2 个文件,+743/-2。 + +## 开发快照 snapshot/v0.1.284 - 2026-11-01 +目标提交:98ccb9366e4070b2ffa2d080186c2ce023e370a0 + +### Tests +- **test(ds02): 负控制落盘并修正 gofmt** +为什么:CI 在 07a9caf 上 format 红(ds02_identity_test.go 未 gofmt),本地那次 +make verify 跑在文件生成之前,不能算数;同时 /v1/encode 负控制只有断言、没有 +落盘证据,复验时无法回看分类结果。 + +改了什么:gofmt 两个测试文件;负控制把实际出站条目与被判为 embedding 的差异 +写进 raw(negative-control),正例仍只对发布段计数。 + +验证:make verify 16 项全绿;随后以新 SHA 重跑 DS02 隔离套件。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/ds02_identity_test.go`、`internal/releaseadapter/v071/ds02_oracle_test.go` +统计:2 个文件,+4/-2。 + +## 开发快照 snapshot/v0.1.283 - 2026-11-01 +目标提交:07a9caf22b61586bd790bd8a78c08be66a5a5e27 + +### Tests +- **test(ds02): 收紧映射/引用/哨兵与进程身份 oracle** +- 新增独立本地号映射 oracle:只接收测试声明的来源行、独立推导的期望目标行、真实目标行 + 与持久账本,按 `(table,column,source,target)` 精确集合比较;目标互换、不存在的目标、 + 被替换的来源、少项/多项/重复、错列都会被判红,而合法账本与真实行逐列相等才算绿。 +- 新增十表范围声明(测试自带 `tenant_id`/`knowledge_base_id` 规则并与真实 schema 列集合 + 对拍),不再复用产品的范围分类生成期望。 +- 新增逐种引用覆盖:`chunk.pre/next/parent_chunk_id`、`wiki_folders.parent_id`、 + `wiki_pages.source_refs/chunk_refs`、`wiki_page_issues.suspected_knowledge_ids`、 + `wiki_log_entries.pages_affected` 等各自 `violations=0` 且非空命中;没有外键契约的软 + 导航字段(`parent_slug`、`in_links/out_links`、`relation_chunks`)显式列为范围外。 +- 新增外围哨兵:`chunk-foreign` 文档完整 `_source` 逐字段比较、`res-other` 对象字节比较, + 两者各有"改坏必须红"的负控制。 +- 新增冻结输入 bundle(十表来源行、目标种子、序列种子、ES 文档、对象字节、完整 DDL, + 时间固定、逐表 hash):旧源码 `6fe98c9` 与当前源码读取同一份文件、同一 sha256。旧侧真实 + 复现 `wiki_log_entries_pkey / SQLSTATE 23505`,新侧同一输入发布成功并通过独立 oracle。 + 差异单列:6fe98c9 在 wiki 之前无法跨租户搬运资源,旧侧归档省略 `resources` / + `resource_bindings` 两表,其余八表与碰撞行逐字节相同;新侧按十表完整运行。 +- 新增测试进程身份断言:runner 注入 test-only nonce/commit/源码树摘要,测试进程自报 + `os.Executable` 的 sha256,与 runner 期望逐字符比较;构建与执行前后各算一次二进制哈希。 +- 新增隔离运行器 v2(`ds02_runner_v2.py`,v1 保留):超时/构建/准备异常都走 finally 清理, + 源码、HEAD、工作区、二进制哈希、bundle 字节、required 缺失/跳过任一变化即失败; + 清理前重核 namespace 前缀与容器 Id/label,读回区分"确实不存在"与认证/连接错误; + `--keep-failure` 保留现场并写 `resources-kept.json`。 + +- DS02 的零 Embedding 断言改为按"发布段"记账:ES 与对象存储两个可达 client 注入同一 + 记录器,按目的地址(路径含 embed/encode 或已知模型域名)识别向量化调用,并补了另一个 + client 走自定义 `/v1/encode` 的负控制;声明范围只到本测试的 client 清单,正式 + API→worker 全链仍留给 DS03。 +- 映射 RED 与摘要 gate 分开:语义变异只改内存账本副本(目标存储不动),归档字节变异只 + 证明 `LoadWriteSet` 摘要拒收,不再拿后者冒充映射断言。 +- 日志内容变异重命名为 `log-content`,不再当作 mapping RED。 +- 历史兼容矩阵改用同一份真实 v071 catalog 初始化,三条本地号序列显式断言存在并逐例比较 + before/after 摘要(不再有空集合"不变")。 +- 旧侧对照措辞修正为"精确旧 SHA 回归对照":不再用 `git rev-list --count` 的 258 当版本号; + 该工作树资源 identity 实际是 0.1.148,没有原发布产物就不做历史考古。 + +- 修正旧 oracle 只检查"每表来源/目标数量与重复"导致的三种盲区(目标互换、错误来源集合、 + 不存在的目标号)。 +- 修正引用断言把 pre/next/parent 混在一个 OR 里、无法证明 parent 分支有覆盖的问题。 +- 修正"序列不变"在最小 schema 上没有序列可比的空覆盖。 +- 修正运行器把 namespace 解析失败静默跳过、删除前不重核所有权、读回把任何非零退出都当 + "不存在"、失败路径不保证清理的问题。 + + 逐列比对目标行内容、逐种引用(父子/标签/资源/知识库/Wiki,含 parent 分支与 + source_refs/chunk_refs/suspected ids)、索引正文与向量、目标及外围对象字节,并按发布段 + 断言 Embedding 调用为 0。期望值由夹具声明推导(来源→目标范围映射、资源定位符与 + `location_hash` 在本测试里重算),不调用被测映射器生成;`parent_slug` 与 in/out_links + 这类没有外键契约的软导航字段明确列为范围外。 +- 新增 Wiki 日志映射 oracle:来源日志数字 id 与目标本地号按独立期望精确配对,并用 + 真实目标行逐列对应;持久账本正例通过同一 checker,语义副本变异(互换/不存在/替换来源/ + 少项/多项/重复/错列)必须红。 +- 新增对照夹具探针(旧代码工作树):精确旧 SHA `6fe98c9` 在同一隔离库/桶/索引里以 + 同一份冻结 bundle 复现 `wiki_log_entries_pkey` / SQLSTATE 23505;当前代码同一输入发布 + 成功并分配本地号,其它知识库的历史行不变。这是**旧源码回归对照**,不是原 0.1.258 发布 + 产物;该工作树的资源身份实际是 0.1.148,`git rev-list --count` 的 258 只是自根提交数。 + +改动范围:`scripts/test-scope.json`、`CHANGELOG.md`、`internal/releaseadapter/v071/ds02_bundle_test.go`、`internal/releaseadapter/v071/ds02_identity_test.go`、`internal/releaseadapter/v071/ds02_legacy_compat_test.go`、`internal/releaseadapter/v071/ds02_mapping_oracle_test.go` 等 9 个 +统计:9 个文件,+1477/-140。 + +## 开发快照 snapshot/v0.1.282 - 2026-11-01 +目标提交:eea0a0ec3b929b91e95d1464310d6eb337c2de11 + +### Tests +- **test(release): 整库发布独立oracle与历史兼容矩阵** +- 新增 `TestDS02FullShapeOracleIsolated`:复用现有十表全形状夹具,用**独立 oracle** + 逐列比对目标行内容、全部父子/标签/资源/知识库/Wiki 引用、索引正文与向量、对象 + 字节,并断言发布过程中 Embedding 调用为 0。期望值由夹具声明推导(来源→目标范围 + 映射、资源定位符与 `location_hash` 在本测试里重算),不调用被测映射器生成。 +- 新增 Wiki 日志映射双射断言:来源日志数字 id 与目标本地号一一对应、无重复、内容 + 元组多重集相等。 +- 新增对照夹具探针(旧代码工作树):0.1.258(6fe98c9)在同一隔离库/桶/索引里以 + 相同输入复现 `wiki_log_entries_pkey` / SQLSTATE 23505;当前代码同一输入发布成功 + 并分配本地号,其它知识库的历史行不变。 +- 新增 `TestDS02LegacyCompatibilityMatrixIsolated`:旧 manifest、V1 台账、空作业/ + 目标身份等缺证明组合必须在正式入口被拒绝,拒绝前后 PG 逐行 + 索引 + 对象版本 + + 序列哈希不变;不做 V1→V2 迁移。 + +- 隔离运行器补齐 provenance:运行前后完整 HEAD/工作区/源码逐文件哈希(含未跟踪)、 + 测试二进制 sha256 与 go build metadata/版本注入、逐用例 PASS/FAIL/SKIP、真实目标 + 身份;一次性资源先登记,结束后按登记名成对删除并逐个读回(容器 inspect 失败、 + 库/索引/桶查询为 0/404)。 + +改动范围:`scripts/test-scope.json`、`CHANGELOG.md`、`internal/releaseadapter/v071/ds02_legacy_compat_test.go`、`internal/releaseadapter/v071/ds02_oracle_test.go` +统计:4 个文件,+1045/-0。 + +## 开发快照 snapshot/v0.1.281 - 2026-11-01 +目标提交:1d2fff8639613eb11514c6d94d805b2f71e86e6c + +### Tests +- **test(release): 正式 worker 与维护恢复入口的 held→恢复闭环验收** +- 新增正式链路验收 `TestFormalMaintenanceRecoveryHeldThenRecovered`(只在 `KG_R38_RUN` 指定的 + 一次性隔离部署里运行,常规运行与 CI 跳过):真实 publish 在 database/resources/index/wiki 四类 + 真实写入完成后,由"校验读回内容与快照不一致"失败进入自动回滚;回滚在 + `restore-finalize-intent` 已落盘、marker 仍停在自己世代时被传输层拒绝 clean 派发(拒绝 = 不转发、 + 不丢响应),窗口保持 held,实例/scope 两个维护记录写 `needs_restore`,写入口返回 409;撤掉一次性 + 故障后 `POST /api/v1/library/maintenance/recover` 以同一 window/job/backup 续作成功,PG 逐行、 + 对象版本集合与 ES `_source` 全量恢复,入口重新返回 200。 +- 该用例中的对象 receipt 全部由真实对象网关写入产生(committed/removed 两条不可变回执 + 对应 + delivery/plan 摘要),没有人工造回执,也没有改核心状态。 + +- 本轮只新增验收与运行器,产品行为、协议与判据均未改变;删除型恢复的 R02-b 缺口保持原样。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_maintenance_recovery_r38_test.go` +统计:2 个文件,+1008/-0。 + +## 开发快照 snapshot/v0.1.280 - 2026-11-01 +目标提交:066dc45a2563e924db49c98327f129c034c8667b + +### Fixed +- **fix(release): 删除型索引恢复不再用 ES 统计计数放行** +- 撤销上一快照的"写计数器栅栏"授权:ES `_stats` 的写计数是**分片生命周期**统计,官方 + 文档明确写"分片迁到另一节点时该分片的统计会被清零";索引 uuid 不变并不绑定统计的 + 生命周期,清零后重新累计到与冻结值相同(ABA)无法排除。统计相等不是不可变证据, + 现在既不落盘也不读取,恢复路径里没有"凭计数放行写入"的分支。 +- 删除型索引恢复(恢复写入是独占 `_create`)只有在**本次尝试开始时不存在任何恢复意图** + 且文档确实缺失时才允许重做("先落意图、后派发"的不变量说明本作业没派发过)。既存的 + `restore-mark-intent` 配上缺失文档没有持久、不可重用的"没写进去"证据,一律 held、 + 零写入。该自动重试缺口已按现状记入验收矩阵,不宣称这是用户接受的能力收窄。 +- 保留可证明的路径:`restore-finalize-intent` 已落盘、真实 marker 仍停在自己那份世代上时 + 继续干净收尾;删除补偿(`Before` 为空)在文档仍是 applied 结果、世代仍是 applied + 回执时重放 `DELETE`。 +- 验收用例新增 ABA 反例:stats 一直返回同一 uuid 同一计数(传输层故障模拟),而真实 ES + 里 marker 已写入又被删除;恢复必须 held。没有重启共享 ES,也没有伪造文档观测。 + +改动范围:`internal/releaseadapter/v071/library_index_effects.go`、`internal/releaseadapter/v071/library_index_restore_claim.go`、`CHANGELOG.md`、`docs/library-acceptance-matrix.md`、`internal/releaseadapter/v071/library_index_known_recovery_test.go`、`internal/releaseadapter/v071/library_index_recovery_gaps_test.go` +统计:6 个文件,+261/-156。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_index_known_recovery_test.go`、`internal/releaseadapter/v071/library_index_recovery_gaps_test.go` + +## 开发快照 snapshot/v0.1.279 - 2026-11-01 +目标提交:ff257064c3bef2fef7baf837cc597eda72e6d11b + +### Fixed +- **fix(release): 索引恢复用写计数器栅栏与世代证明续作** +- 索引恢复不再把"文档缺失"当成"证明从未发送":恢复 marker 写成功、回执丢失、随后又被删除, + 与从未写过在索引里完全同形。删除型恢复写入是**独占 `_create`**,现在只有在第一次派发前 + 冻结的**索引写计数器栅栏**(索引 uuid + `index_total` + `delete_total`)逐位未变时才允许重做; + 栅栏推进、栅栏缺失或读取失败一律 held,不认领(B19 不放宽)。 +- `restore-finalize-intent` 已落盘、干净收尾写入在派发前被拒绝后,重试不再永久 held:标记体 + 仍停在自己那份世代(内容 == 冻结标记体,`_seq_no`/`_primary_term` == 意图回执)即证明干净 + 写入没有落地,按同一 CAS 继续收尾;内容或世代对不上仍然 held。 +- 删除补偿(`Before` 为空、恢复写入是 `DELETE`)在派发前被拒绝后同样可以续作:文档仍是 + applied 结果、世代仍是 applied 回执才重放 `DELETE`;文档已变或世代不符仍然 held。 +- 下一条旧快照里"删除即缺失、判据只有世代"的表述仅适用于更新型恢复;删除型以本条冻结的 + 索引写计数器栅栏为准。 + +- 对象验收证据改用 `ListObjectVersions`(键 + versionId + ETag/字节数/删除标记):`ListObjectsV2` + 只枚举当前可见对象,不能证明历史版本。 + +改动范围:`internal/releaseadapter/v071/library_index_effects.go`、`internal/releaseadapter/v071/library_index_restore_claim.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/library_index_known_recovery_test.go`、`internal/releaseadapter/v071/library_index_recovery_gaps_test.go` +统计:5 个文件,+619/-47。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_index_known_recovery_test.go`、`internal/releaseadapter/v071/library_index_recovery_gaps_test.go` + +## 开发快照 snapshot/v0.1.278 - 2026-11-01 +目标提交:de867078c6da4a7aeac0a403b89cb8c9cf218e61 + +### Changed +- **style(release): gofmt 索引恢复验收测试** +CI 的 format 项只在已跟踪文件上扫,提交前测试文件还是未跟踪状态,本机 make verify +没看到它;补一次 gofmt,不改行为。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/releaseadapter/v071/library_index_known_recovery_test.go` +统计:1 个文件,+1/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_index_known_recovery_test.go` + +## 开发快照 snapshot/v0.1.277 - 2026-11-01 +目标提交:b15633d37c340f4ee971aca845aa0ebe5db4fb98 + +### Fixed +- **fix(release): 索引恢复在确定没写进去时按世代证据重做** +- 恢复/回滚的索引步骤若在**持久意图之后、派发之前**确定失败(控制流拒绝),重试不再被永久判成 + `unknown restoration lacks exact operation marker/content/CAS` 而 held:只要观察到的内容仍等于自己 + 确认过的 applied 结果(删除即缺失)、且世代与那份回执完全一致,就说明这份恢复意图一次都没到达 + 索引,按原 before-image 重做不会覆盖任何别人的字节。 +- 判据仍然只有世代,不认"内容看起来一样":内容对但世代对不上、应用期停在标记态却对不上标记体或 + 世代,一律保持 held(B19 不放宽)。 +- 新增一次性恢复写入派发边界(`restoreMarkerFault`,生产恒为 nil),用来把"边界上确定没写进去"与 + "写入结果不确定"分开演练;真实 ES / PostgreSQL / 对象存储仍是唯一权威。 + +改动范围:`internal/releaseadapter/v071/library_index_effects.go`、`internal/releaseadapter/v071/library_index_restore_claim.go`、`internal/releaseadapter/v071/library_production_target.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/library_index_known_recovery_test.go` +统计:5 个文件,+337/-9。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_index_known_recovery_test.go` + +## 开发快照 snapshot/v0.1.276 - 2026-11-01 +目标提交:c938a4f4bf257be90ba8963328ec5368e92b2903 + +### Fixed +- **fix(freeze): 归档改为不可覆盖副本,保留 current 到新请求写入成功** +- 维护窗口的旧请求归档不再 `rename` 走当前 journal:先写一份**不可覆盖**的副本 + (`O_EXCL`,同名已存在时必须字节相同、不同则明确拒绝),当前 journal 保持可用, + 直到新一次尝试通过 `save` 原子替换成功。此前若 `pending/prepare/inventory/save` 任一步失败, + 目录里连一份可读的 current 都不剩;进程在归档与写入之间中断也一样。 +- 归档名按**被归档请求自己的 attempt** 派生(v1 旧记录为 `attempt0`),新尝试从 `attempt+1` 开始, + 因此连续失败的多代不会撞名;撞名且内容不同一律拒绝,不覆盖。 +- 开新尝试额外要求 `source`、`target_fingerprint` 与当前实例一致,且入口若关过必须已有 + `gate_reopened` 证据;否则仍按"需要恢复"拒绝。 + +改动范围:`internal/libraryfreeze/window.go`、`CHANGELOG.md`、`internal/libraryfreeze/archive_safety_r31_test.go` +统计:3 个文件,+224/-10。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/archive_safety_r31_test.go` + +## 开发快照 snapshot/v0.1.275 - 2026-11-01 +目标提交:ba1989b04217b0b268e1068ab1ff40ac270029af + +### Fixed +- **fix(freeze): 收尾后的同作业续作可开新尝试并归档旧请求** +- 维护窗口协调器:上一次尝试在**没有写过任何目标内容**的情况下失败并已正常收尾后,同一作业的续作 + 现在可以开新一次尝试,而不是被 `freeze request already exists` 挡住、让合法的 resume 变成一条没有 + 恢复路径的 202。旧请求不删除也不覆盖:归档到 `archive/-attempt.json`,新请求用 + `superseded_from` 指向它,`job / operation / backup` 身份保持不变。 +- 一旦上一次尝试已经 `contentRisk=true`(写过目标),仍然拒绝开新窗口并保留原来的 + `recovery or a new request is required` 语义 —— 那种情况必须按原 before-image 恢复,不能重来一遍。 +- 已成功收尾的窗口继续按幂等回放处理,不重复停写者/关入口的副作用。 + +改动范围:`internal/libraryfreeze/freeze.go`、`internal/libraryfreeze/window.go`、`CHANGELOG.md`、`internal/libraryfreeze/resume_attempt_r30_test.go` +统计:4 个文件,+187/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/resume_attempt_r30_test.go` + +## 开发快照 snapshot/v0.1.274 - 2026-11-01 +目标提交:a98828dc4a5eb064066e7bfd31a9e513b2f56a34 + +### Fixed +- **fix(library): 续作在事务内校验阶段并原子入队** +- 续作改为拒绝 `stage=rollback` 的作业(`409 library_resume_requires_recover`):这类作业已经回滚, + 正确出口是维护恢复(`/library/maintenance/recover`),它绑定原 `window_job_id` 并使用原 before-image; + 继续续作等于丢开前镜像重来。拒绝文案按维护记录如实给出窗口是否仍被持有,不一律宣称"仍持有"。 +- 阶段/状态/执行权与队列载荷全部在**事务内锁住的那一行**上校验并重建(`SELECT … FOR UPDATE`): + 调用方手里的旧快照不再参与决策,避免用过期 stage 复活已变化的作业;并发重复续作只有一个成功, + 另一个得到 `library_resume_busy` 或 `library_resume_state_changed`,不会重复投递。 + +改动范围:`internal/httpapi/library_restore.go`、`internal/platform/postgres/library_jobs.go`、`CHANGELOG.md`、`internal/httpapi/library_flow_test.go`、`internal/httpapi/library_resume_r26_test.go`、`internal/platform/postgres/library_resume_r25_test.go` +统计:6 个文件,+363/-43。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_flow_test.go`、`internal/httpapi/library_resume_r26_test.go`、`internal/platform/postgres/library_resume_r25_test.go` + +## 开发快照 snapshot/v0.1.273 - 2026-11-01 +目标提交:e292b6f674bcefe893c6f295190c68ec5d20b5c1 + +### Fixed +- **fix(library): 续作改为单事务入队,回滚作业转维护恢复** +- 作业续作(`POST /api/v1/library/jobs/{id}/resume`)不再先提交 `state=pending` 再单独入队。 + 旧实现里队列行主键由业务作业 id 派生(`QueueJobIDFor`),第二次续作时 `INSERT` 撞主键, + 入队失败返回 500,而作业已经被改成 `pending` 且没有可用队列行 —— 从此永久卡住, + 后续恢复请求全部被 `409 library_restore_active` 挡住。 + 现在「结束上一代 attempt + 业务作业置 pending + 队列行 upsert(含租约检查)」在同一个事务里完成: + 入队失败整笔回滚、作业状态不变;租约仍有效时返回 `library_resume_busy` 而不是半态。 +- 续作改为拒绝 `stage=rollback` 的作业(`409 library_resume_requires_recover`):这类作业已经回滚并 + 保留维护窗口,正确出口是维护恢复(`/library/maintenance/recover`),它会绑定原 `window_job_id` + 并使用原 before-image;继续续作等于丢开前镜像重来。 + +改动范围:`internal/httpapi/library_restore.go`、`internal/platform/postgres/library_jobs.go`、`CHANGELOG.md`、`internal/platform/postgres/library_resume_r25_test.go` +统计:4 个文件,+217/-22。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_resume_r25_test.go` + +## 开发快照 snapshot/v0.1.272 - 2026-11-01 +目标提交:30686dd288448a243bcfcd9e5e3742060ffef379 + +### Fixed +- **fix(ui): 版本记录错误详情复用已解析响应体,不再二次读 Body** +- 版本记录客户端:500/409 的异常消息复用已解析的响应体,不再二次 `response.json()` + (真实 `Response` 上会抛 `Body already read`,被 catch 吞掉后只剩状态码);错误详情现在稳定带出服务端 `error` 与 `code`。 + +- `web/src/versionHistory.test.ts`:用真实 `Response` 构造 500,断言消息含 `error` 与 `code`(伪 mock 可重复 `json()` 会掩盖 double-read)。 + +改动范围:`web/src/versionHistory.test.ts`、`web/src/versionHistory.ts`、`CHANGELOG.md` +统计:3 个文件,+25/-11。 + +## 开发快照 snapshot/v0.1.271 - 2026-11-01 +目标提交:8ce78e28fabf99eb0093abb7d051c779fda71658 + +### Docs +- **docs(changelog): 开发快照按内容命名,不写死开发号** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`CHANGELOG.md` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.270 - 2026-11-01 +目标提交:044fb37b2c7ae14153db35920fec4350963bd9ad + +### Changed +- **style(test): gofmt 补文件结尾换行** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/version_history_sync_state_integration_test.go` +统计:1 个文件,+1/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/version_history_sync_state_integration_test.go` + +## 开发快照 snapshot/v0.1.269 - 2026-11-01 +目标提交:e34306b2dbbe58bde2de613be62291fe84c4cdcc + +### Fixed +- **fix(ui): 已释放窗口不再渲染 held 文案,版本页不再把 500 说成未接线** +- 发布管理:作业失败但维护窗口已释放时,不再显示"目标保持维护、入口未开、收尾未完成"的 held 诊断; + 改为如实记录"作业失败(维护窗口已释放)"。只有仍被持有的失败作业才给 held 处置文案(此前仅按 failed 渲染)。 +- 版本记录:同步进行中的 run(`finished_at` 为空)不再让整页 500。部署重启会自动触发一次同步, + 原先该行的空时间戳扫描失败 → `Get /api/v1/version-history` 返回 500。 +- 版本记录页:请求失败时不再声称"当前运行版本未知"或"版本记录未接线",并显示服务端返回的 + `error`/`code`(例如 `version_history_unavailable`),把"未配置"和"后端故障"分开。 + +- `internal/platform/postgres/version_history_sync_state_integration_test.go`:进行中 run(NULL finished_at) + 读取同步状态与分页都必须成功。 +- `web/src/releaseTargetsMaintenance.test.tsx`:已释放的失败作业不出现 held 文案;仍持有时保留。 +- `web/src/VersionHistory.test.tsx`:加载失败时不出现"未接线/未知",并显示服务端原因。 + +改动范围:`internal/platform/postgres/version_history.go`、`web/src/App.test.tsx`、`web/src/App.tsx`、`web/src/BackupScheduleSection.test.tsx`、`web/src/BackupScheduleSection.tsx`、`web/src/DingtalkSessionControl.test.tsx` 等 76 个 +统计:76 个文件,+12036/-2503。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/version_history_sync_state_integration_test.go`、`web/src/documentFixture.ts` + +## 开发快照 snapshot/v0.1.268 - 2026-11-01 +目标提交:943b617b1f778a5a75db13eb2d9e50b8180f3569 + +### Tests +- **test(isolated): 支持保留隔离演练现场供本机 UI/API 验收** +- 隔离演练支持 `KG_R8_KEEP_SCENE=1` 保留现场:不删索引/对象桶,并把网关审计公钥与派生令牌落到 0600 文件, + 便于对本机一次性目标做真实 UI/API 验收;默认不设该变量时仍在断言后精确清理。 +- 产品代码、数据契约与生产行为不变;不构成生产授权,也不替代真实演练证据。 + +- 保留现场只作用于一次性 PG/ES/MinIO;生产闸口、凭据、ACL 与宿主操作仍未授权。 + +改动范围:`CHANGELOG.md`、`internal/releaseadapter/v071/formal_worker_integration_test.go`、`internal/releaseadapter/v071/library_writeset_integration_test.go` +统计:3 个文件,+31/-0。 + +## 开发快照 snapshot/v0.1.266 - 2026-11-01 +目标提交:99055a582eea241909089ec78ee6fadc45fdcdb6 + +### Tests +- **test(library): 补孤儿校验锚点失败与介质失效的真实隔离用例** +孤儿校验:缺 knowledge_id 的索引文档被拒绝、未知 knowledge_id 如实报孤儿、 +数据库层中途失败(真实 statement timeout,SQLSTATE 57014)冒泡为错误而不是 +"没有孤儿"。 + +锚点:真实 PG 约束拒绝写版本锚点时,作业在任何阶段写入前失败、目标零写入、 +版本表不留本次行;去掉故障后同一请求写满四阶段。 + +介质失效:归档 part 缺失/摘要不符时恢复停在内容层,保持 held + RestoreBlocked, +服务与入口都不恢复、校验不再跑。 + +改动范围:`scripts/test-scope.json`、`cmd/worker/library_anchor_r13_integration_test.go`、`internal/libraryfreeze/recover_window_test.go`、`internal/releaseadapter/v071/data_orphan_r13_test.go` +统计:4 个文件,+239/-0。 + +## 开发快照 snapshot/v0.1.267 - 2026-11-01 +目标提交:90ab1c3a5ede520fecd2d71362c0699dd16e992b + +### Docs +- **docs: 记录已接受的游标与 clean 边界** +- 发布作业详情在目标仍被维护时返回维护状态与人为处置说明(持有作业、入口状态、目标身份、下一步),不提供"强制成功"入口;缺归属证据的收尾保持维护锁定。 +- 发布或恢复完成后,Wiki 与日志页面的分页游标视为失效:页面提示刷新后重新分页(本地编号会被重新编排),不把旧游标的错位当成数据损坏。 + +改动范围:`CHANGELOG.md`、`docs/library-data-contract.md` +统计:2 个文件,+9/-0。 + +## 开发快照 snapshot/v0.1.265 - 2026-11-01 +目标提交:0df0e3cb1e076e1a19bfd7da9eee38ba9ea29c47 + +### Changed +- **feat(library): 作业详情显示维护状态与人工处置** +发布作业详情在目标仍被维护时返回 maintenance(持有作业、入口状态、目标身份、 +备注、是否已释放)与 next_step;作业本身继续带 operation_id/attempt_epoch/预备份/ +目标快照与错误说明,能定位到"哪个意图、哪份基线、哪个目标"。 + +不提供强制成功/强制开门入口:缺归属证据的收尾保持维护锁定,按预备份与校验证据 +人工核验后再放行(用户已接受的可用性边界)。页面同时提示发布/恢复后旧分页游标 +失效、需要刷新重新分页。 + +改动范围:`internal/httpapi/library_flow.go`、`web/src/ReleaseTargets.tsx`、`internal/httpapi/library_held_detail_r13_test.go` +统计:3 个文件,+115/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_held_detail_r13_test.go` + +## 开发快照 snapshot/v0.1.264 - 2026-11-01 +目标提交:f4bd1980bfeda7f4d5e9f5138d13e025726ca82e + +### Docs +- **docs: 记录 r12 数据契约的软删除/索引/游标边界** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-data-contract.md` +统计:1 个文件,+10/-0。 + +## 开发快照 snapshot/v0.1.263 - 2026-11-01 +目标提交:8df19f9368193a83393f2422b7808fe7e0b00077 + +### Tests +- **test(library): 补软删除共享资源与索引 oracle 隔离用例** +真实本机 PG/ES/MinIO 上补五组场景:7 张带 deleted_at 表的软删除 before-image 与 +恢复逐列复原;同 _id 属其它知识库时外部文档 _source/seq_no/term 逐字节不变; +正文/向量/metadata 任一篡改都让独立 oracle 变红;晚阶段失败后既有对象精确版本 +不变;同 ID 共享资源在 handle/定位/字节三类差异下写前拒绝。另在复用用例里补 +"no-op 复用不改写对象版本"的断言。 + +需隔离服务的用例继续在 test-scope.json 显式声明:普通 CI 跳过不算通过。 + +改动范围:`scripts/test-scope.json`、`internal/releaseadapter/v071/data_state_r12_test.go`、`internal/releaseadapter/v071/library_reuse_integration_test.go` +统计:3 个文件,+301/-0。 + +## 开发快照 snapshot/v0.1.262 - 2026-11-01 +目标提交:8d838d9cb40143b1400b95757aeb9bb0f03be2ab + +### Changed +- **feat(library): 整库发布身份、写集合与收尾边界** +执行时真实时钟与持久授权回读(撤销/过期即停并保留预备份)、写集合写前落盘、 +租约与 attempt 身份、恢复队列与原发送身份分离、成功收尾必须释放维护后再登记终态。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/release-channel-e2e/main.go`、`cmd/release-diag/main.go`、`cmd/release-worker/runtime.go`、`cmd/worker/library_attempt_timing.go` 等 82 个 +统计:82 个文件,+4808/-550。 + +## 开发快照 snapshot/v0.1.261 - 2026-11-01 +目标提交:5afd8c0140e3929ed2165057e3154c372cf6eea3 + +### Docs +- **docs: 更新整库数据契约与变更记录** +- 新源捕获在同一只读事务中封存表结构、约束、索引与序列定义;发布前核源/目标目录一致性。旧快照与V1台账不改写、不重算历史摘要。 +- 写前验证归档关系闭包与目标已有关系;源导出拒绝被JOIN掩盖的缺父、跨父和未知资源owner,不靠删除孤儿消除差异。 +- 目标预备份、计数与恢复统一覆盖knowledge/Wiki正文及嵌套metadata的稳定资源引用;保留历史孤儿和共享资源原值,未知引用表示明确拒绝。 +- 恢复前重新检查本次新建资源的未知绑定和外库正文/metadata引用;有计划外读者时在任何索引/对象/PG修改前held,父键锁内再次检查所有正文和绑定,防止先删对象才发现不可恢复。 +- 实时模型准入拒绝空模型名,避免将未知模型误判为可兼容。 + +- 数据契约仅在一次性本机PG/ES/MinIO验证;正常业务租约、云权限、WeKnora源码及业务Schema不改。clean未知仍held,不降低所有权证明。[逐表契约与覆盖边界](docs/library-data-contract.md)。 + +- 独立恢复队列与原发布发送身份分离,可信控制源核持久请求、当前租约、原 operation、来源、目标、预备份和 WriteSet;恢复意图不再被原发布自动接管抢跑,恢复中断由新执行代次继续。 +- 索引 before-image 恢复写入采用绑定原计划的暂态标记;真实提交但丢响应后,仅完整标记正文和CAS证据匹配才允许收尾,不重发已提交的标记写入。完成回执同时校验精确代次。 + +- 发布或恢复的 clean-finalization 丢失响应时,相同干净正文不能证明写入归属,继续 held;旧恢复unknown缺少写前标记证据也不自动认领。 +- 自动授权只读角色增加 jobs 表 SELECT 核验;仍禁止其治理写权限。正式独立恢复与故障链仅为本机隔离验收,正常租约未缩短,未部署业务或变更云侧凭据/ACL/网络;整体验收未闭合前不发布。[身份、证据与剩余边界](docs/object-write-gateway.md)。 + +- 正式 worker 在持久化原写集合摘要后,通过数据身份向网关申请绑定当前执行代次的授权;可信控制域独立读取治理锚点和原计划,普通 worker 不持有控制凭据。 +- 网关持久保存不可变 attempt/grant 交付记录;发送身份与当前授权 epoch 分离,新执行代次可以对账原发送并继续未发送对象,未知请求仍阻断交接。 + +- **破坏性**:关闭旧 ObjectImporter staging/promotion、legacy release-worker 和写型诊断/E2E 入口;正式发布 stack 对所有对象 provider 强制网关,不回退直写。只读 capture 保留。[接入与回退边界](docs/object-write-gateway.md)。 + +- 自动授权核验原预备份锚点、目标、有效 attempt/owner/租约及原计划内容;拒绝跨代次替换计划。控制读取使用独立 SELECT-only 登录角色,旧手动控制端点在自动模式关闭。 +- 正式 worker 已在专属本机隔离栈完成 PG 后、对象提交后两次 SIGKILL 与原计划接管;独立读回验证三代授权、一份原发送回执和唯一对象版本。演练仅在显式隔离构建中缩短租约,正常构建维持10分钟,不接受环境变量缩短。真实部署独占与整模块验收尚未完成,仍禁止部署部分候选;未切生产凭据、ACL 或网络,未新增云对象写入。 + +- 新增对象写入网关与独立控制客户端:冻结逐key允许集合、持久epoch/intent/精确版本回执、同key串行化与未知请求阻断交接;网关模式拒绝无计划Put/Copy和直写回退。保持资源UUID、物理路径及历史摘要不变。 + +- 已确认归属及CAS代次的索引补偿删除,在真实删除成功却丢失响应后可按精确缺失完成恢复;重新创建的文档代次仍拒绝接管,不盲重发删除。 + +- 网关逐请求绑定协议与目标,控制/数据身份分离;拒绝不完整回执、无持久日志、只读或失败的日志事务、未登记计划及缺失/过期的审核签名。治理证据禁止更新、删除和截断。 +- 此候选尚未完成正式worker控制grant自动交付、跨新epoch接管及真实写入口独占验证,禁止部署部分业务实现;生产凭据/ACL/网络未改,未进行网关云OSS端到端写。[配置、信任边界与剩余项](docs/object-write-gateway.md)。 + +- 管理员可从发布页选择历史快照,核验原捕获执行器身份、期望指纹及清单后关联当前来源。关联只追加治理审计证据,不重新封存、不修改原摘要,也不代替发布授权。 + +- 归档初始化同步根目录及父目录,目录同步失败时拒绝继续,避免仅同步文件仍遗漏目录项持久性。 + +- 每次同步冻结实际目标预备份与独立计划;重复 Wiki slug 在写前拒绝,晚期失败仍清理本次新增对象并保留复用资源。 +- 索引逐 ID 记录不可变回执;带操作归属标记的写入在真实服务提交后丢失响应时,可由新进程确认并补偿,不凭相同正文猜测归属。 +- 对象阶段进程中断验证覆盖实际 MinIO 提交后、回执落盘前的 SIGKILL 与持久计划恢复。 +- 不可变副作用证据拒绝祖先软链接,并逐级同步新目录的父目录;同步错误返回失败,不再只同步最末两级。 + +- 增加原生 OSS 精确版本协议及显式隔离测试入口,已完成明确隔离前缀内的真实版本写入、精确读回与无残留清理,尚未替代生产条件创建路径;无充分证据的 unknown 分支、跨域 fencing 和全部验收仍未闭合,禁止发布部分实现。[兼容与未完成边界](docs/library-migration-contract.md)。 + +- 整库发布保留 UUID 和复合键,Wiki 数值身份与大整数内容不再经过浮点数;chunks、标签与 Wiki 日志使用持久化的目标本地编号映射,接管时校验来源和映射覆盖。 +- worker 装饰器转发既有写集合接管能力;写集合及资源复用证据不可覆盖,返回前同步文件与目录。 +- 预备份保留实际范围内的软删除行和关联;同租户异知识库不再被误判为来源残留,关系表按精确二元组识别。 +- 索引按全局文档 ID 拒绝跨知识库覆盖,采用 create/乐观并发条件写入并检查逐项结果;共享资源内容差异写前拒绝,相同资源按证据只读复用。 + +- 本开发快照尚未完成整模块验收,禁止据此开启发布写门:对象版本化精确清理仅完成 MinIO 局部验证,OSS 适配、执行栅栏、来源候选/UI 和真实 worker 连续两次中断验证仍待闭合。[兼容与未完成边界](docs/library-migration-contract.md)。 + +- 发布在预备份后、规划前与首笔写入前按治理库授权行重新判定:授权被撤销或过期即停,保留预备份且不写目标;不再只信内存里的有效期。 +- 整库发布绑定序列归属及完整迁移目录,识别分块与 Wiki 的规范嵌套引用;未知结构或跨父引用在写入前拒绝,保留正常资料的发布与恢复路径。 +- 发布使用执行时真实时钟,在预备份后与首个业务阶段前复核授权;预备份绑定对象精确版本,拒绝读取期间被替换的对象(包括同字节新版本)。 + +改动范围:`CHANGELOG.md`、`docs/library-data-contract.md`、`docs/library-migration-contract.md` +统计:3 个文件,+212/-0。 + +## 开发快照 snapshot/v0.1.260 - 2026-11-01 +目标提交:8febe12d7df42a0a945445d709a379c52de9d4c1 + +### Tests +- **test(library): 隔离数据契约与 r11 身份自然键矩阵** +真实本机 PG/ES/MinIO:v071 目录镜像 10 模式、结构引用/游标 15 模式、 +8 表×2 外部范围身份矩阵、13 模式自然键(含软删除与合法同 parent 正例)、 +备份期间三注入、收尾故障与授权撤销两个真实治理 PG 用例。 + +需隔离服务的用例在 test-scope.json 显式声明:普通 CI 跳过不算通过。 + +改动范围:`scripts/test-scope.json`、`cmd/worker/library_attempt_timing_test.go`、`cmd/worker/library_contract_adopter_test.go`、`cmd/worker/library_finalize_r11_integration_test.go`、`cmd/worker/library_grant_revoke_r11_integration_test.go`、`cmd/worker/library_lease_test.go` 等 62 个 +统计:62 个文件,+7329/-167。 + +## 开发快照 snapshot/v0.1.259 - 2026-11-01 +目标提交:5eef13b6151a1f4b56861568cf38b3008df6c534 + +### Changed +- **feat(object-gateway): 统一对象写网关与不可变治理日志** +方案A:所有对象写入经 loopback 网关,持久计划/grant/intent/逐版本 receipt 与 +attempt→grant delivery 落在治理 PG;scope 由 provider+endpoint+bucket 决定, +同一物理桶只允许一个写域。版本 append 不等于 CAS,未锚定计划在首笔业务写前拒绝。 + +本提交只含网关与其适配层接线、协议文档;生产独占、凭据切换与桶设置未改动。 + +改动范围:`cmd/object-gateway-control/main.go`、`cmd/object-gateway/main.go`、`internal/objectgateway/acquire.go`、`internal/objectgateway/audit.go`、`internal/objectgateway/claim_context.go`、`internal/objectgateway/gateway.go` 等 29 个 +统计:29 个文件,+3287/-0。 + +### Tests +本次提交包含测试/夹具变更:`cmd/release-worker/legacy_gate_test.go`、`internal/objectgateway/acquire_integration_test.go`、`internal/objectgateway/audit_test.go`、`internal/objectgateway/gateway_integration_test.go` 等 12 个 + +## 开发快照 snapshot/v0.1.258 - 2026-10-30 +目标提交:d0c6d57283f4a5bc16db2dfd97d2eb3c737aaad7 + +### Fixed +- **fix(storage): 按提供方选择整库对象寻址方式** +- 整库发布与恢复按存储提供方选择寻址方式:MinIO 保留路径式,OSS/S3 使用虚拟主机式,不再因强制路径式而被 OSS 拒绝资源上传。 + +改动范围:`internal/librarystack/stack.go`、`CHANGELOG.md`、`internal/librarystack/stack_test.go` +统计:3 个文件,+28/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/librarystack/stack_test.go` + +## 开发快照 snapshot/v0.1.257 - 2026-10-30 +目标提交:ee9d0787946c7cef2f7fe3b918c34cd6e6818aa9 + +### Fixed +- **fix(release): 分配目标分块序列并保持原值恢复** +- 发布分块时由目标数据库分配本地 `seq_id`,不再复制源库序列而撞上其它知识库的唯一索引;文档和分块 UUID 不变。 +- 失败恢复仍重放预备份中的原始序列,并保留整实例逐行核验;发布内容校验仅排除分块的本地序列,不放宽正文和其它字段校验。 + +改动范围:`internal/releaseadapter/v071/library_production_target.go`、`scripts/test-scope.json`、`CHANGELOG.md`、`internal/releaseadapter/v071/library_chunk_sequence_integration_test.go`、`internal/releaseadapter/v071/library_chunk_sequence_test.go` +统计:5 个文件,+117/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_chunk_sequence_integration_test.go`、`internal/releaseadapter/v071/library_chunk_sequence_test.go` + +## 开发快照 snapshot/v0.1.256 - 2026-10-30 +目标提交:a50cd734233e07fff941d6a3778f48f4d95c6de5 + +### Fixed +- **fix(deploy): 校验云执行器授权配置与稳定启动** +- 云发布执行器开启写入前检查授权人和私有 token 文件,按固定容器路径只读挂载;配置缺失在替换旧容器前拒绝。 +- 部署脚本连续检查容器运行及重启计数,启动失败或重启循环不再误报成功;部署回归纳入必需契约检查。 + +改动范围:`deploy/cloud-test/run-release-executor.sh`、`deploy/cloud-test/test_deployment.py`、`deploy/cloud-test/test_release_executor.py`、`CHANGELOG.md`、`deploy/cloud-test/deployment_test.go` +统计:5 个文件,+163/-4。 + +### Tests +本次提交包含测试/夹具变更:`deploy/cloud-test/deployment_test.go` + +## 开发快照 snapshot/v0.1.255 - 2026-10-30 +目标提交:286f1fb375331658e86ebb84278e061a5d2954a1 + +### Fixed +- **fix(maintenance): 避免健康探针连接阻塞入口排空** +- 维护健康探针在请求完成后关闭连接,避免自身的空闲 keep-alive 被入口计为在途连接并导致排空超时;不改变业务连接或长流的排空规则。 + +改动范围:`internal/librarymaintenance/target.go`、`CHANGELOG.md`、`internal/librarymaintenance/probe_drain_test.go` +统计:3 个文件,+46/-0。 + +### Tests +本次提交包含测试/夹具变更:`internal/librarymaintenance/probe_drain_test.go` + +## 开发快照 snapshot/v0.1.254 - 2026-10-30 +目标提交:3e4020c327c7226859da6cd7d9b5812ebfd5d519 + +### Fixed +- **fix(drill): 支持空目标并在探针写入前落盘恢复证据** +- 恢复演练使用独立标签作为漂移探针,支持空目标,不再依赖改写已有文档;探针写入前持久化精确写集合并标记内容风险,恢复只清理本次新建身份。 + +改动范围:`cmd/librarydrill/canary.go`、`cmd/librarydrill/main.go`、`CHANGELOG.md`、`cmd/librarydrill/canary_test.go` +统计:4 个文件,+118/-30。 + +### Tests +本次提交包含测试/夹具变更:`cmd/librarydrill/canary_test.go` + +## 开发快照 snapshot/v0.1.253 - 2026-10-30 +目标提交:56f8f4fd7ea659b897cc127ed7760429ef1fc6bf + +### Fixed +- **fix(library): 统一空目标概览与首次同步准入** +- 发布概览与同步提交统一探测未知目标:真实空库允许进入首次同步条件判断,无法连接不再误报为非空库。探测超时后拒绝放行,授权、恢复演练及写入闸口仍须满足。 + +改动范围:`internal/httpapi/library_flow.go`、`CHANGELOG.md`、`internal/httpapi/library_overview_empty_test.go` +统计:3 个文件,+135/-6。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_overview_empty_test.go` + +## 开发快照 snapshot/v0.1.252 - 2026-10-30 +目标提交:7ab2eb1ccf4c3bc716ca6c103c163b9b8ae49cfe + +### Fixed +- **fix(library): 按目标身份隔离版本授权与执行历史** +- 切换发布目标后,不再把旧实例的当前生效版本、预备份、恢复候选和授权当成新实例事实;当前指针按目标与对内/对外范围独立保存,旧作业不可被新目标接管。 +- 本机隧道与云端直连共用逻辑目标身份,源封存保持可见;历史记录及证据保留,不自动归属新目标。 + +- **破坏性**:治理库迁移 077 增加目标身份,须停旧消费者后升级;已有目标绑定证据时禁止直接降级。[升级与回退边界](docs/library-target-identity.md)。 + +改动范围:`cmd/api/main.go`、`cmd/worker/main.go`、`internal/appconfig/library_target.go`、`internal/platform/postgres/library_jobs.go`、`internal/platform/postgres/library_operations.go`、`internal/platform/postgres/migrations/000077_library_target_identity.down.sql` 等 15 个 +统计:15 个文件,+473/-72。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_target_integration_test.go`、`internal/appconfig/library_target_test.go`、`internal/platform/postgres/library_target_test.go`、`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.251 - 2026-10-30 +目标提交:323f9ae50230dc00608f32b9ad53e32f02973cc6 + +### Fixed +- **fix(handoff): 在执行器容器内读取云端归档清单** +- 快照清单通过指定执行器容器读取,不再将容器内归档路径当作 SSH 宿主机路径;避免已导入的快照被误报不存在,保留真实缺失与执行错误的区别。 + +改动范围:`internal/libraryhandoff/handoff.go`、`CHANGELOG.md`、`internal/libraryhandoff/ssh_manifest_test.go` +统计:3 个文件,+62/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryhandoff/ssh_manifest_test.go` + +## 开发快照 snapshot/v0.1.250 - 2026-10-30 +目标提交:52c0186172319c08ab3d62bb4473707e0492fbc1 + +### Fixed +- **fix(handoff): 修复搬运默认上限及云端接收目录挂载** +- 修复快照搬运未显式设置大小和超时上限时默认值丢失,导致正常包被按零字节上限拒绝;默认上限为 8 GiB、超时 10 分钟,显式配置仍生效。 +- 云发布执行器挂载独立的接收包目录,宿主机与容器路径保持一致,修复文件已传到主机但执行器无法装入或导出的故障。 + +改动范围:`internal/libraryhandoff/handoff.go`、`deploy/cloud-test/run-release-executor.sh`、`deploy/cloud-test/test_deployment.py`、`CHANGELOG.md`、`deploy/cloud-test/README.md`、`internal/libraryhandoff/defaults_test.go` +统计:6 个文件,+51/-3。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryhandoff/defaults_test.go` + +## 开发快照 snapshot/v0.1.249 - 2026-10-30 +目标提交:657fd251fbcc2d1e8d1724b182ac98ec608cfa07 + +### Fixed +- **fix(deploy): 显式启动发布worker并声明受控运行身份** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`deploy/cloud-test/run-release-executor.sh`、`deploy/cloud-test/test_deployment.py`、`deploy/cloud-test/README.md` +统计:3 个文件,+10/-1。 + +## 开发快照 snapshot/v0.1.248 - 2026-10-30 +目标提交:468ae6491891607645ff9c90c1358eb37e288729 + +### Fixed +- **fix(deploy): 修正云执行器套接字与凭据挂载契约** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`deploy/cloud-test/pg-forward.sh`、`deploy/cloud-test/release-executor.env.example`、`deploy/cloud-test/run-release-executor.sh`、`deploy/cloud-test/test_deployment.py`、`deploy/cloud-test/README.md`、`deploy/cloud-test/deployment_test.go` +统计:6 个文件,+118/-10。 + +### Tests +本次提交包含测试/夹具变更:`deploy/cloud-test/deployment_test.go` + +## 开发快照 snapshot/v0.1.247 - 2026-10-30 +目标提交:06c2db19fcd7a4bf3c7f0bbee0686ca730c4933e + +### Fixed +- **fix(library): 删除 handoff 路径残留的无条件 stage 写** +- 删除 handoff 路径残留的无条件 `UpdateLibraryStage`:它与 `BeginLibraryHandoff` 事务里的写重复, + 并在 Begin 返回到这次写之间给交接留出竞态窗口。现在 handoff 的所有阶段写只发生在 + Begin/Complete/Fail 三个持有 job 行锁的事务方法里;新增源码契约测试防止回流。 + +改动范围:`cmd/worker/library_handoff.go`、`scripts/test-scope.json`、`CHANGELOG.md`、`cmd/worker/library_handoff_stage_contract_test.go` +统计:4 个文件,+92/-2。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_handoff_stage_contract_test.go` + +## 开发快照 snapshot/v0.1.246 - 2026-10-30 +目标提交:70c181370db2339c9157c3f41c055e388e158e3e + +### Fixed +- **fix(library): handoff 阶段事务化、回传空页重置与目标基线原产地** +- handoff 的阶段与所有权收敛为一组事务方法:`BeginLibraryHandoff`(事务内建/推进 running 阶段并确认所有权)、 + `CompleteLibraryHandoff`(同一事务:投递发布队列作业 + 交接 queue_job_id + 阶段 succeeded)、 + `FailLibraryHandoff`(同一事务:阶段 failed + 作业 failed)。阶段行按 (job_id, stage) upsert, + 不再依赖 `SyncStages` 里是否预置了 handoff 行。 +- 回传单轮抽成 `pullRound`:**空页重置游标**(引用数正好是 pageSize 整数倍时,否则游标停在最老处、 + 之后新增的引用永远取不到),且游标跨轮保留(单轮 20 页只是预算)。 + +- 传输阶段在真实 UI 上不可见:此前 running 阶段是无条件 `UpdateLibraryStage`(读 owner 再写,存在竞态), + 成功阶段在交接后按旧 owner 写必然 0 行,且 `library_job_stages` 里根本没有 handoff 行。 + 现在阶段创建/推进/成功/失败都与 job 行的所有权判定在同一事务里完成,交接提交后阶段一定是 succeeded。 +- 身份判定漏掉"目标权威基线":`library_baseline` 是目标侧真实捕获,但只登记 `production_effective` + (证据 action=target_baseline_init / origin=target_baseline)。现在原产地按捕获证据判定: + 封存=源、预备份=目标、带基线证据的 effective=目标;普通 effective 与 recovery_verified 只是引用, + 不决定原产地;无捕获证据或源/目标同时成立仍拒绝。 + +改动范围:`cmd/worker/library_handoff.go`、`internal/platform/postgres/library_jobs.go`、`CHANGELOG.md`、`cmd/worker/library_handoff_chain_test.go` +统计:4 个文件,+420/-246。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_handoff_chain_test.go` + +## 开发快照 snapshot/v0.1.245 - 2026-10-30 +目标提交:8a68ed1819afb159e7d269f7b92819e539527794 + +### Fixed +- **fix(library): 交接所有权 CAS、回传游标跨轮与快照原产地** +- **handoff → release 的交接改为按所有权 CAS**:`EnqueueAndAttachLibraryJob` 接受"调用方声明的前驱队列作业号", + 只允许"当前无人持有"或"当前正是这个 handoff"时替换;任意替换一律拒绝。发布/恢复的发布作业 ID 仍是 + `QueueJobIDFor(job)`,接管/续作路径不变。 +- 回传枚举的游标**跨轮保留**:单轮仍有 20 页工作量预算,但下一轮从上次位置继续,读到不足一页才回到起点, + 所以第 2001 条以后的引用不会被每轮重置的游标永久漏取。 +- 快照**原产地只由捕获事件决定**:`test_sealed`(源)与 `production_prebackup`(目标)互斥; + `production_effective` / `recovery_verified` 只是引用同一份快照的完成事件,不再参与身份推断 + ——"先封存、后发布成功"的正常链路不会再被自己的完成事件判成身份冲突。 + +- **正常流程必现阻断**:API 先关联 handoff 队列作业,而交接只接受"未关联"状态,导致每一次正常发布都被自己的 + 所有权检查挡住。另修 handoff 队列作业号:`jobs.id` 为 varchar(36),原实现拼接后缀超长,插入即失败; + 现在由 `governance.LibraryHandoffQueueJobID` 生成合法且确定的 ID。 +- **迟到 handoff 污染已交接作业**:失败出口(含 Push 错误)改为按所有权 CAS,只在本 handoff 仍持有业务作业时 + 才写 state/stage/recovery;已交接或已终态时零修改,也不再写"未进入维护窗口"这种只对本次执行成立的结论。 + 阶段写入同样带所有权判定(所有权只由 `library_jobs.queue_job_id` 决定,与阶段行是否存在无关)。 + +改动范围:`cmd/worker/library_handoff.go`、`internal/governance/library_flow.go`、`internal/httpapi/library_flow.go`、`internal/platform/postgres/library_jobs.go`、`scripts/test-scope.json`、`CHANGELOG.md` 等 11 个 +统计:11 个文件,+499/-56。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_handoff_chain_test.go`、`cmd/worker/library_handoff_integration_test.go`、`cmd/worker/library_handoff_pagination_test.go`、`internal/httpapi/library_handoff_queue_test.go` + +## 开发快照 snapshot/v0.1.244 - 2026-10-30 +目标提交:9300865d23436a7426b535e1577ab2557b5dc57c + +### Fixed +- **fix(library): 搬运复用前复验字节、回传游标分页与发布队列事务交接** +父独立审查退回的四个 finding,逐项修在本包内,不改架构。 + +1 复用路径没复验字节:remoteEvidence 只读清单+摘要就宣布可用。现在新增 +libraryctl snapshot-verify(整版逐部件重算)并在复用前调用;blob 缺失/篡改必须 +在入发布队列之前失败。新增真实 local 跨进程用例:清单不动、删 blob / 改 blob 后 +再 Push 必须拒绝。 + +2 回传枚举被固定 LIMIT 50 截断:LibraryJobSnapshots 只取成功作业且按摘要字典序。 +改为 LibrarySnapshotCandidates:合并 library_versions、library_jobs.snapshot_id、 +library_jobs.target_snapshot_id 三处引用,按 (created_at, snapshot_id) 游标分页; +回传循环翻页到不足一页为止。失败/待恢复作业留下的预备份、第 51 条以后、历史恢复 +输入快照都能被枚举。另修"本地只有清单、blob 已损坏就永远跳过":改用整版 Load 判定, +并给 Unpack 增加显式 RepairCorruptBlobs(只在权威云端读回时开启)。 + +3 身份映射错误:搬运拿目标 tenant/KB 去核对封存快照,而封存清单写的是**源**身份 +(云测试验收目标 a8e9c8ad/2e980686 与源 KB 必然不同)。现在 expectedSnapshotIdentity +按版本台账判定:test_sealed → 用部署注入的源绑定(LIBRARY_SOURCE_TENANT_ID / +LIBRARY_SOURCE_KB_*);预备份/生效/恢复版本 → 用目标绑定;没有登记或两类冲突一律拒绝。 +身份永远来自独立绑定与台账,不接受包自证。 + +4 两步入队有竞态:Enqueue 成功后云执行器可能立刻领取并开始维护,Attach 失败却把作业 +标失败并声称"未维护"。改为 EnqueueAndAttachLibraryJob 单事务(锁行 + 插入 + 关联 + +状态守卫),要么都成功要么都不发生;重复执行幂等,已关联别的队列作业则拒绝。 +另修终态重放:AdvanceLibraryJob 无状态守卫,迟到的失败出口会把已成功作业改写; +新增 AdvanceLibraryJobIfActive(仅 pending/running 可推进),拿不到就明确记录"未覆盖"。 + +测试:libraryhandoff 用例覆盖跨租户/错误 scope/清单在而字节坏/本地损坏自愈; +隔离 fixture 库覆盖"校验通过才入队、失败不入队"、>50 条分页与失败作业预备份、 +幂等重放不重复投递、迟到失败不改写终态。 + +未部署、未推送、未构建镜像;真实跨主机 SSH 链路仍未验证。 + +改动范围:`cmd/libraryctl/main.go`、`cmd/worker/library_handoff.go`、`internal/appconfig/config.go`、`internal/libraryhandoff/handoff.go`、`internal/librarysnapshot/transfer.go`、`internal/platform/postgres/library_jobs.go` 等 10 个 +统计:10 个文件,+640/-64。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_handoff_integration_test.go`、`cmd/worker/library_handoff_pagination_test.go`、`internal/libraryhandoff/handoff_test.go` + +## 开发快照 snapshot/v0.1.243 - 2026-10-30 +目标提交:6400fd1bbedb1e9e7d58c3e60b8b9f69d8c903a4 + +### Changed +- **feat(library): 快照搬运先于维护、双向校验与云端版本回传** +- **发布/恢复作业先搬运、后执行**:快照搬运接线(`LIBRARY_HANDOFF_*`)时,API 把作业投递成 + `library.snapshot.handoff`(capture 角色消费),由它把不可变快照送到云端 release-executor 并 + **双向校验**(本机 `Load` 逐 blob 重算 → 云端 `snapshot-unpack` 再逐 blob 重算 → 版本清单摘要自证 → + scope/tenant/知识库身份与治理库作业一致)之后,才投递真正把目标置入维护窗口的 `library.sync|restore`。 + 搬运失败/超时/校验不过时发布作业**根本不存在**,目标不会被停用、不写任何知识。 +- 新包 `internal/libraryhandoff`:`Push`(本机→云端)与 `Fetch`(云端→本机)共用一套校验与暂存规则; + 远端命令是固定 argv(容器名/路径来自部署配置,调用方只能给快照 ID 与期望身份);同一快照用本机 + flock 串行化,云端内容寻址 + Link 幂等;同 ID 异内容直接拒绝;暂存 bundle 用后即删,归档本体不自动清理。 +- capture 角色新增**云端版本回传**扫描:按治理库已成功作业的 `snapshot_id`,把本机缺失的云端快照 + `Fetch` 回本机归档,因此页面/API 既有的列表、详情、差异、存储与历史恢复路径读到的都是真实字节。 +- `libraryctl snapshot-push` / `snapshot-fetch`:运维可直接搬一份快照并打印逐 blob 身份用于对拍。 + +- `LIBRARY_WORKER_ROLE` 的副作用边界继续收敛:搬运与回传只注册在 capture 角色;若把搬运接线到 + `full` 角色(同时消费发布作业)启动即失败,避免与云端执行器抢同一批作业。 +- `library_jobs` 的孤儿收拾按角色负责的 kind 过滤;治理库 DDL 仍只属于控制面进程。 + +- 入口闸门不再在 10 秒静默后掐断上游流:建连超时与读超时分离(连上即 `settimeout(None)`), + 半关闭按 TCP 语义单向传播,避免响应被截断;排空超时仍以 exit 2 表达失败。 + +改动范围:`cmd/api/main.go`、`cmd/libraryctl/main.go`、`cmd/worker/library_handoff.go`、`cmd/worker/main.go`、`internal/httpapi/library_flow.go`、`internal/httpapi/library_restore.go` 等 18 个 +统计:18 个文件,+1801/-12。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_handoff_integration_test.go`、`internal/httpapi/library_handoff_queue_test.go`、`internal/libraryhandoff/handoff_test.go` + +## 开发快照 snapshot/v0.1.242 - 2026-10-30 +目标提交:e6472b0ec67684a6e0e09372d67699ac9454f0b3 + +### Changed +- **feat(library): 云测试发布目标接入的角色分离、快照搬运与图谱门禁** +为什么改:发布执行链是"与目标同机"假设写死的(本机 docker.sock、本机 drain.json、 +本机 Redis/PG),把目标换成云测试不能只改展示 URL;直接搬整套 worker 又会抢走 +本机封存/QA/周报队列并按本机配置写错目标。所以先把"谁消费哪段队列"变成显式角色, +再补齐跨主机搬运与目标侧门禁。 + +角色(LIBRARY_WORKER_ROLE=full/release-executor/capture)不只影响 handler 列表, +还决定是否拥有 DDL(release-executor 只读校验迁移版本,落后即拒绝启动)、 +收拾哪些 library_jobs.kind、是否跑 release 接管扫描、是否起 scheduler/QA/周报/探测。 +capture 保留本机原有全部业务;release-executor 只挂 sync/restore/recover/baseline。 + +快照跨主机:新增内容寻址的 bundle(版本清单先落、blob 按 sha256 命名、清单最后 +Link 作提交点),pack/unpack 幂等且拒绝覆盖与同名不同内容;执行器使用前仍逐 blob +重算 sha256,因此传输不可变有两道证据。 + +目标侧门禁:目标知识库 graph_enabled=true 时拒绝发布(发布只写 PG/ES/OSS,不写 +Neo4j;未知也拒绝),只读检测、不改目标配置、不做图谱迁移。 + +云端入口:新增 drain 感知的 ingress 闸门(建连超时与读超时分离,半关闭单向传播, +SIGTERM 排空后写 drain.json 并 exit 0/2)、compose 叠加片段(app 不再发布端口、 +frontend 与 adapter 的入口收敛到闸门、指向真实网络 weknora-test-v071-network)、 +执行器启动脚本与治理 PG 反向隧道脚本(默认 unix socket,不占 TCP)。 + +验证:deploy/cloud-test/test_gate.py 13 项全 PASS(含 12 秒静默流、半关闭、2MiB 完整、 +排空 exit 0/active_at_close=1、排空超时 exit 2、上游不可达不挂住);go test 相关包全绿; +scripts/ci_checks.py 16 项全部 success(EXIT=0,1241 用例通过/0 失败/191 已声明可选集成跳过); +云测试只读预检 17 项 / 6 FAIL,FAIL 项即当前部署缺口(无闸门、app 仍发布端口、 +adapter 与 frontend 仍直连 app、云端归档根未建),未部署、未改云端任何状态。 + +改动范围:`cmd/libraryctl/main.go`、`cmd/worker/library_release_handlers.go`、`cmd/worker/main.go`、`internal/appconfig/config.go`、`internal/appconfig/library_role.go`、`internal/librarysnapshot/transfer.go` 等 24 个 +统计:24 个文件,+2409/-117。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_role_test.go`、`internal/appconfig/library_role_test.go`、`internal/librarysnapshot/transfer_test.go`、`internal/platform/postgres/library_reconcile_test.go` 等 5 个 + +## 开发快照 snapshot/v0.1.241 - 2026-10-30 +目标提交:a7d643635ad8de1fae3a1e65dd93edc436e17c28 + +### Fixed +- **fix(library): 守卫按行取作业身份、同作业多代次、原作业续作与执行域准入** +- **续作沿用原业务作业,只新增执行代次**:维护窗口 owner、freeze journal、预备份与写集合都按业务作业 id 落盘,因此不再为续作另造 `-resume` 作业行;发起续作会显式结束上一代执行(旧代次标记 superseded,行保留)。 +- 执行域准入改为**可执行校验**:worker 注册整库发布 handler 之前,读目标实况(知识库/租户/schema/索引)、旧 release 路由绑定的目标知识库、以及未释放的维护窗口持有者;任一读不到、与配置不一致、或旧路由指向同一个知识库,就拒绝执行(明确失败,不做任何维护动作,也不停止既有服务)。 +- 维护状态/入口/释放的守卫**按被更新行自己的作业号**取身份(此前把 `$1` 硬编码成作业号,而维护类入口的 `$1` 是 scope/instance,导致正常写入与释放全部匹配不到作业);同一代码路径同时用"作业号不匹配必须失败"的负例锁住。 +- 同一作业允许**多个不可变执行代次**:认领递增 epoch 并新增 attempt 行(不再复用唯一索引改写旧代次),重复调用幂等续租,别人持有未过期租约时明确让路;被接管的旧代次标记 abandoned,迟到的 write/renew/finish 一律按 `ErrLibraryEpochStale` 拒绝。 +- 首次锚点登记改为事务内 `FOR UPDATE` + 只填空 + 逐字段回读核对;`NULL` 与零值不再混淆(真实 PG 上验证,未知 ≠ 0)。 +- current 的次序键取**意图创建时间**:旧意图的迟到事实被 `ErrLibraryCurrentStale` 拒绝,不会把当前生效版本推回历史;恢复通过新的 `kind=recover` 意图(内容更旧、意图更新)推进。 +- 版本事件冲突只做**加法**(已有键永远赢、来源作业/意图/登记时刻冻结),且只有真正新插入(`xmax=0`)才推进 current,纯重放不再二次推进。 +- 集成测试护栏:`TEST_DATABASE_URL` 必须指向看起来是一次性的测试库(或在确属一次性库时显式声明),并先清理上次中断留下的同名 schema;`kg-pgtest-*` 专用容器内一用例一 schema,结束即删。 + +改动范围:`cmd/worker/library_baseline.go`、`cmd/worker/library_completion.go`、`cmd/worker/library_domain.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go`、`cmd/worker/library_writer.go` 等 26 个 +统计:26 个文件,+1553/-217。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_backup_reuse_test.go`、`cmd/worker/library_identity_helpers_test.go`、`cmd/worker/library_takeover_historical_integration_test.go`、`cmd/worker/library_takeover_integration_test.go` 等 7 个 + +## 开发快照 snapshot/v0.1.240 - 2026-10-30 +目标提交:afe9affc2aea5b84d936c257ad853a58770e4d2a + +### Changed +- **style: gofmt 新增的三个文件** +为什么:远端 CI 的格式门在推送后判红(run 258,job build,format=failure), +本地 `make verify` 那次跑的是"拉取前"的文件集,漏掉了随后新增/改写的三个文件。 +失败项:internal/httpapi/library_intent_test.go、internal/jobs/lease_loss_test.go、 +internal/platform/postgres/library_protection.go。 + +怎么验:`gofmt -l internal/ cmd/` 输出为空;`python3 scripts/check_format.py` rc=0 +(Go:✅,前端:✅)。只改空白,不改逻辑。 + +改动范围:`internal/platform/postgres/library_protection.go`、`internal/httpapi/library_intent_test.go`、`internal/jobs/lease_loss_test.go` +统计:3 个文件,+3/-3。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_intent_test.go`、`internal/jobs/lease_loss_test.go` + +## 开发快照 snapshot/v0.1.239 - 2026-10-30 +目标提交:98294b9583c0c877f5dd31db61e5146ff548010b + +### Fixed +- **fix(library): 统一执行身份、终态收尾、准入与数据身份** +- **发布意图与执行代次分开**:`library.sync` / `library.restore` / 续作改用 operation(一次明确意图)+ attempt(执行代次)。同一个 `operation_key` 重发命中同一条意图(不重复执行),不带或换键就是新意图——A→B→A 的第三次发布不再拿到第一次的历史成功作业。请求体新增可选 `operation_key`。 +- 版本台账改为**不可变事件 + current 指针**:不同 operation 引用同一快照是两条事件,`source_job_id`/`evidence`/`created_at` 不再被后来的执行覆盖;"谁是当前"由单调次序的条件更新决定。治理库新增迁移 76(`library_operations` / `library_attempts` / `library_current_versions`),不触碰 WeKnora Schema、源码或镜像。 +- 按预备份恢复成功后登记**恢复事实**并推进 current:页面、差异与后续作业看到的是恢复出来的内容身份,而不是继续宣称已失败的新版本生效;原失败作业与失败版本保持不变。 +- 接管或恢复时,执行目标消费已持久化写集合里的复用决定;**读不懂计划的实现直接拒绝写入**(不再"账本说没动共享对象、实际又重写一遍")。 +- 预备份合同改为目标实时状态(schema/模型/映射),不再把来源快照的声明当目标合同。 + +- 半写窗口接管时,前置检查(入口/后台静默/停写者)任一失败都不再重新开放目标:持久 `ContentRisk` 优先于"本进程还没写"。 +- 服务启动或健康校验失败时不再调用开入口:入口保持关闭,交恢复流程处理。 +- journal 已是 `succeeded` / `recovered` 的终态重放可从持久事实重建收尾凭据,不再永久卡住维护锁。 +- 心跳失租会立刻取消 handler;业务状态、阶段进度、版本事件与维护锁释放都带执行代次(epoch)守卫。 +- 迟到的旧代次不能推进 current,也不能释放/改写另一个代次的窗口。 +- 续作沿用原 operation 与首次锚点,不另造丢谱系的新意图。 +- 收尾续作(finalize-only)与正常出口共用同一证据构造:缺少真实字节数或写集合摘要即拒绝登记生效版本。 +- 通用 `POST /api/v1/jobs` 拒绝 `library.*` 保留类型;worker 在动维护前核对业务行、kind/scope 与队列关联。 +- 全库搬运改写定位符时重算 `location_hash = sha256(最终 physical_path)`,与文档级导入共用同一实现。 +- 整库发布在副作用前核对目标**实时**模型契约:同维不同模型、模型不可读、dirty schema 一律拒绝。 +- 容量门禁接到 worker 执行前(预算不足或读不到即拒绝);存储预览按全量依赖判定保护,保护依据读不到时拒绝生成可删候选,备份类型只认显式登记。 +- 发布页:状态读取核 HTTP 状态与响应结构;按预备份恢复纳入轮询并在维护关闭后读回终态;Wiki 差异覆盖标题/摘要/父级变化。 +- CI 版本契约门同时核进程退出码、必需测试真 PASS 与负例失败归因(编译/网络错误不算预期断言生效)。 + +改动范围:`cmd/worker/library_admission.go`、`cmd/worker/library_baseline.go`、`cmd/worker/library_capacity.go`、`cmd/worker/library_completion.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go` 等 52 个 +统计:52 个文件,+2818/-195。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_capacity_test.go`、`cmd/worker/library_writeset_resume_integration_test.go`、`cmd/worker/library_writeset_test.go`、`internal/httpapi/library_flow_test.go` 等 12 个 + +## 开发快照 snapshot/v0.1.238 - 2026-10-30 +目标提交:4f0852a94dd36aca68b646f192b4dc2e6ba51ac1 + +### Fixed +- **fix(ci): 契约门同时核进程退出码与负例归因** +为什么:版本契约门只看 `--- PASS: X` 两行,rc1 取到却没用。同一进程里别的测试 +失败时 go test 仍会打印这两个 PASS 行、退出码非 0——那种"绿"证明不了这一轮测试 +成功,负构建因编译/网络错误非 0 也会被当成"断言还活着"。审计 F19 的原始触发即此。 + +改了什么:判据抽成 `judge_contract_positive` / `judge_contract_negative`,正向要求 +rc=0 + 每个必需测试真 PASS + 无 FAIL 事件 + 未跳过;负例要求非 0 且失败在预期断言上, +命中编译/网络/工具链标记即判红。判据的反例自检 `contract_judgement_problems()` 同时在 +`make verify` 的 contracts 步骤与 `--self-test` 里跑,判据被改坏在本地就红。 +POLICY_REVISION 提到 ci-checks/4。 + +怎么验:`python3 scripts/ci_checks.py --self-test` rc=0(10 汇总分支 + 10 契约反例); +`--internal-contracts $(command -v go)` rc=0;反例探针(假 go 打印两个 PASS 后 exit 1) +现在 rc=1,修复前同一输入 rc=0。 + +改动范围:`scripts/ci_checks.py` +统计:1 个文件,+104/-21。 + +## 开发快照 snapshot/v0.1.237 - 2026-10-30 +目标提交:c2ab658033ffaa2ae8001134528214fe14f25c07 + +### Docs +- **docs: 记录写集合证据被整份覆盖的陷阱** +新增 trap 20:复用预备份时重新造一份证据,会让已登记的写集合锚点被整份 +抹掉(现场 backup_bytes:0 + writeset_* 全丢)。写明症状、根因、负例优先的 +判据与修法,含预备份版本行「一条只属于首次登记的作业」这一归属语义。 +AGENTS.md 索引表那一行受写入保护、需主人批准,本次未改(见交接单待批准项)。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/agents/traps.md` +统计:1 个文件,+43/-0。 + +## 开发快照 snapshot/v0.1.236 - 2026-10-30 +目标提交:d0310761b6a266722cbe772ab997727c69454183 + +### Fixed +- **fix(worker): 接管复用预备份不再抹掉写集合证据** +- **崩溃接管复用预备份时,已经登记的写集合证据被整份覆盖,恢复链断在第二次中断**: + `BackupPrepared` 原先每一轮都新造一份证据,而版本登记是整份替换 + (`evidence = EXCLUDED.evidence`)。接管路径上复用声明只带快照 id(没有字节数), + 写集合预检又只在首轮执行,于是 `writeset_digest`/`writeset_rows` 被抹掉、 + `backup_bytes` 记成 0;同一作业再中断一次就再也取不回写集合,只能在 + `verify_effective` 上失败(2026-10-30T00:58Z 演练实例 `…-1793321916382871758-retry`, + 摘要 `fddd98cf70…` → 空)。现在证据按"同一作业 + 同一份预备份"合并进已持久化那一行, + 身份不符一律拒绝;接管复用从首次登记的证据取回真实字节(缺失即拒), + 生效版本证据统一构造。同一作业的写集合只允许落定一次,摘要冲突即失败。 + +改动范围:`cmd/worker/library_handlers.go`、`cmd/worker/library_writeset.go`、`scripts/test-scope.json`、`CHANGELOG.md`、`cmd/worker/library_writeset_resume_integration_test.go`、`cmd/worker/library_writeset_test.go` +统计:6 个文件,+643/-17。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_writeset_resume_integration_test.go`、`cmd/worker/library_writeset_test.go` + +## 开发快照 snapshot/v0.1.235 - 2026-10-30 +目标提交:780ab988c4676fab1816415fa35d15288343063a + +### Fixed +- **fix(worker): 历史快照恢复接管按作业身份与新授权判定** +真实现场 2026-10-30T00:19Z:新恢复作业恢复历史快照 fc595511(已被 0d982f76 +顶替),写入 database 后被 SIGKILL;重启接管时 classifyTakeover 只要看到 +该快照有 SupersededAt 就判 superseded,没有判这行历史版本属于哪个作业, +把历史那次发布被顶替当成这次作业过期,作业 failed/prebackup,而恢复文案 +又写"无需恢复",目标半写且维护锁未释放。 + +修法:被顶替分支先分清归属——本作业 finalize 成功或版本行属于本作业 ⇒ 仍 +拒绝;历史行属于别的作业时,仅当本次是 restore、授权签发于顶替之后、scope +与 instance 两把锁仍由本作业独占且指纹一致、预备份登记属于本作业且清单字节 +仍等于摘要,才按 fresh 继续重跑整条链路。任一证据缺失/读不到仍拒绝。 + +同时把续作早退统一走 failLibraryJobOrTakeover:有持久化预备份时保留 +needs_restore 并点名 backup,不再写"无需恢复"。 + +验证: +- 先写失败回归(历史行+新授权 ⇒ 期望 fresh,实得 superseded),修后绿; +- 真实 PostgreSQL 正负例:授权撤销/实例锁释放/预备份归属被改/清单字节被改 + 均回到 superseded;续作早退保留备份与 needs_restore; +- 本地 make verify 16 项必需检查全绿(identity 本地档案跳过)。 + +改动范围:`cmd/worker/library_finish.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_takeover_resume.go`、`scripts/test-scope.json`、`cmd/worker/library_finish_test.go`、`cmd/worker/library_takeover_historical_integration_test.go` 等 8 个 +统计:8 个文件,+518/-30。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_finish_test.go`、`cmd/worker/library_takeover_historical_integration_test.go`、`cmd/worker/library_takeover_historical_test.go`、`cmd/worker/library_takeover_integration_test.go` + +## 开发快照 snapshot/v0.1.234 - 2026-10-29 +目标提交:c57c22c65e7e9f9a9a739b80597c65c34760e7b5 + +### Docs +- **docs: 记录接管证据陷阱与判据变化** +- traps §19:汇总日志与阶段文案都不是接管证据;认领/决策行必须带作业 id 且晚于 + 杀点;prebackup 允许 succeeded,但要证明复用同一份而不是重做; +- traps 索引第 19 行待主人批准(AGENTS.md 受写入保护),草稿在 + docs/agents/traps-index-pending.md; +- 验收矩阵:S04-b 记"写链路已实测、脚本级验收未成立",并补一节 + "这一轮到底验到了什么/还没成立什么"。旧 FAIL 证据不改写。 + +verify: make verify overall success(16 项)。文档不含会漂移的统计数字。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/agents/traps-index-pending.md`、`docs/agents/traps.md`、`docs/library-acceptance-matrix.md` +统计:3 个文件,+77/-2。 + +## 开发快照 snapshot/v0.1.233 - 2026-10-29 +目标提交:8a3aa800a3e0ecd59a4878557f3ec47ee99f891c + +### Fixed +- **fix(scripts): 演练判据改用认领与决策行并校验预备份复用** +v4 判据(都带作业身份,且时间戳必须晚于杀点): + +- `scripts/takeover_log_contract.py` 是唯一判据:认领行 + 决策行都要有本作业 id、 + target/backup 一致;只有汇总行、别的作业的认领行、杀之前的旧日志三种一律红; + `--self-test` 11 条(正例 2 / 负例 9)进 `make verify`(新增第 16 项必需检查); +- `prebackup` 不再强制 `skipped`:真实接管是复用后重跑整条链路,阶段记 succeeded。 + 但**不取消备份校验**,改为证明复用同一份——快照 id、清单字节与 mtime、 + 版本行首次登记时刻都不变,且证据里有 `backup_reused=true`; +- 杀之前把预备份清单 sha256/mtime 与版本行登记时刻拍进 evidence; +- summary.json 增加 takeover_decision / claim_at / backup_registered_at。 + +verify: 判据自检 11 条全过;用真实 Go 输出行形状的正例日志 rc=0;旧形状(只有 +汇总行)rc=2 且理由点名"汇总";`--check` 前置体检 13 项全 pass(含新增 +preflight_log_contract);make verify overall success。 + +改动范围:`scripts/ci_checks.py`、`scripts/library-kill-takeover.sh`、`scripts/takeover_log_contract.py` +统计:3 个文件,+404/-17。 + +## 开发快照 snapshot/v0.1.232 - 2026-10-29 +目标提交:85b3596e0ef5c9fc54f35b691b92721bd80b3b25 + +### Fixed +- **fix(postgres): 复用预备份时保留首次登记时刻** +崩溃接管会复用崩溃前那份预备份;BackupPrepared 会再登记一次版本行,而 +`created_at = EXCLUDED.created_at` 把"备份什么时候拍的"洗成了接管时刻 —— +事后无法区分"复用"和"崩溃后重做了备份",而后者正好把半写状态当成可回滚基线。 + +- 只对 `production_prebackup` 取 LEAST(首次登记时刻只早不晚); +- 生效版本行仍写当前时刻(重新激活一个旧快照后,页面要显示最近一次生效); +- 真实 PG 用例两条负例:撤掉保留规则 ⇒ 红;规则漏到所有 kind ⇒ 红。 + +verify: 一次性库 kg_takeover_reuse_test 上 PASS; +负例 1(撤掉 CASE)→ "复用后登记时刻被改写";负例 2(CASE 去掉 kind 条件)→ +"生效版本重新激活后时刻";make verify overall success。 + +改动范围:`internal/platform/postgres/library_jobs.go`、`scripts/test-scope.json`、`cmd/worker/library_backup_reuse_test.go` +统计:3 个文件,+115/-2。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_backup_reuse_test.go` + +## 开发快照 snapshot/v0.1.231 - 2026-10-29 +目标提交:6bdc17474c344fd8f343363515fa6ee67bd936c2 + +### Changed +- **feat(worker): 接管认领与决策落结构化日志** +2026-10-29T19:56Z 的 after:database 演练里,worker 只留下汇总行 +`library job takeover sweep: 1 resumed, 0 failed without backup`:既不说哪个 +作业,也不说它是否真被本进程认领(被别的 worker 抢走、attach 失败都只影响 +计数)。演练脚本要的老文案只在"新建队列行"那条路径打印,真实走的是"队列行 +已存在"那条 ⇒ 判据与实现各说一套,演练 exit 5。 + +- 新增 `library job takeover claimed at=… kind=… scope=… target=… + backup=… queue=… path=…`:claim + attach 都成功之后才写,两条认领路径共用 + (path=enqueue / existing-queue); +- 新增 `library job takeover decision=…`:分类后、动手之前写,含 + finalize_only。只留阶段表的话,同快照收尾能把它伪造成完整续作; +- resume 的 store 参数抽成接口,单测才能证明"没认领/attach 失败时一行都不写" + (变异验证:把认领行提到 attach 之前 ⇒ 红;删掉 existing-queue 那行 ⇒ 红); +- 接管复用的预备份在证据里写 `backup_reused=true`:分不清"复用"与"崩溃后重做 + 备份",后者会把半写状态当成可回滚基线。 + +verify: go test ./cmd/worker -run 'TestTakeover|TestLedgerBackupStore' -count=1 全绿; +make verify overall success(16 项必需全绿)。 + +改动范围:`cmd/worker/library_handlers.go`、`cmd/worker/library_takeover_log.go`、`cmd/worker/library_writeset.go`、`internal/libraryrelease/release.go`、`cmd/worker/library_takeover_log_test.go` +统计:5 个文件,+342/-6。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_takeover_log_test.go` + +## 开发快照 snapshot/v0.1.230 - 2026-10-29 +目标提交:d5447280018614224e902f34595b57df35b258e7 + +### Tests +- **test(worker): 真实PG断言拒绝接管的落库效果** +拒绝接管不只要"分类对",还要三处落库都对:library_jobs 终态 failed + 保留预备份 +基线 + 恢复指引里带得上那个基线;library_maintenance 的 released_at 仍为 NULL、 +state=needs_restore(目标可能半写,释放等于谎称已恢复)。 + +用一次性库 kg_takeover_foreign_test 实跑: +- TestFailLibraryTakeoverKeepsMaintenanceAndNamesBackup PASS (1.35s) +- 负例:把恢复指引改成"无需恢复" ⇒ 同一用例红(library_takeover_integration_test.go:117) +- TestTakeoverRefusesSameSnapshotRegisteredByAnotherJob / 超出历史窗口那条仍 PASS + +改动范围:`scripts/test-scope.json`、`cmd/worker/library_takeover_integration_test.go` +统计:2 个文件,+71/-0。 + +## 开发快照 snapshot/v0.1.229 - 2026-10-29 +目标提交:4a70c2a848f687ed94dbd841a6ed10f3e5243219 + +### Fixed +- **fix(scripts): 杀之前等 docker events 监听就位** +SIGKILL 与事件监听抢在同一秒时,die 事件可能早于 attach 被错过,症状是误报 +"没有 exitCode=137 证据"(假红)。加 1s 等待,并用注释写清它只会假红、不会假绿。 + +改动范围:`scripts/library-kill-takeover.sh` +统计:1 个文件,+3/-0。 + +## 开发快照 snapshot/v0.1.228 - 2026-10-29 +目标提交:d0fe257531b7412c60b238e44c4d25f6f82a5cb7 + +### Docs +- **docs: 记录接管身份陷阱与演练判据变化** +- **同一个快照由别的作业登记成生效版本时,接管会把它当成本作业"只差收尾"**: + `classifyTakeover` 原先只看"当前生效版本的目标快照是否等于我的目标快照",而版本行 + 主键由 `(kind, scope, 快照)` 决定、写入是 upsert(会改写 `source_job_id`), + 所以"生产上有这个快照"被误当成"本作业写完了"。2026-10-29T19:16Z 的真实后果: + 作业只写完了 `database` 阶段(`resources/index/wiki/verify` 仍 pending), + 人工拉起后被判 `succeeded`,并把生效版本归属改写成自己。现在分类带作业身份 + (`effectiveOwnedBy`):归属别的作业 ⇒ `foreign_effective`,走拒绝接管的失败出口 + (不写目标、不释放维护、终态失败并指明预备份),不再按同快照收尾。 + 同一份身份判定与基线共用,快照相等不再单独构成"完成"证据。 + +- **中断演练脚本改用"进程崩溃"语义,并把期望内容按快照推导**: + `scripts/library-kill-takeover.sh` 不再 `docker kill`(那是 Docker 手动停止, + `unless-stopped` 也不拉起,上一轮就因此空等 420s、最后靠人工 `docker start`)。 + 现在向容器 PID1 从宿主机发 SIGKILL,用 `docker events` 的 `exitCode=137` 作证据, + 限期等 restart policy 拉起;策略没起来才显式 `docker start`,并在证据里记成 + `restart_source=manual_start`,绝不当自动恢复。期望内容按目标快照的 manifest 推导 + (`knowledges/chunks/wiki_pages`),`resources` 不进默认期望(同租户其它知识库的 + 资源会保留)。新增:`--kind sync|restore`、`--snapshot` 强校验、目标快照等于中断前 + 生效版本时**杀之前就拒绝**、五个写入阶段全部 `succeeded` 才算完整续作、 + 独立写集合校验器缺失即失败、首屏证据读回用 `owner_scope`。 + +改动范围:`CHANGELOG.md`、`docs/agents/traps-index-pending.md`、`docs/agents/traps.md` +统计:3 个文件,+72/-0。 + +## 开发快照 snapshot/v0.1.227 - 2026-10-29 +目标提交:1d70059d328977fcae01589992d16f5d407dbe5f + +### Fixed +- **fix(scripts): 中断演练改用进程崩溃语义并按快照推导期望** +上一轮为什么空等 420s:docker kill 走 Docker API 的"手动停止",unless-stopped +也不拉起,容器一直躺着,最后由人工 docker start 才接管 —— 那不是崩溃自恢复。 + +改法: +- 命中后从宿主机向容器 PID1 发 SIGKILL(同 uid 才允许,先证明能杀);退出码证据取 + docker events 的 die exitCode=137 —— restart 后 State.ExitCode 会被重置成 0, + 事后 inspect 读不到 137。 +- 限期等 restart policy 拉起(同容器/同镜像才算);策略没起来才显式 docker start, + 并记 restart_source=manual_start,绝不当自动恢复。 +- 期望内容按目标快照 manifest 推导(knowledges/chunks/wiki_pages),resources 不进 + 默认期望(同租户其它知识库资源会保留,trap-16)。 +- --kind sync|restore + --snapshot 强校验;目标快照等于中断前生效版本时**杀之前 + 就拒绝**(同快照收尾不能当完整续作验收)。 +- 新增 verify_stages_complete:database/resources/index/wiki/verify 全部 succeeded + 才算完整续作(上一轮这四个还是 pending,却被判成功)。 +- 独立写集合校验器缺失即 fail(旧版记 canonical=not_run 还 pass)。 +- save_evidence 读实例维护用 owner_scope(旧版读 scope 列,SQL 报错被吞)。 + +验证(已跑): +- bash -n 通过;--check 全绿(READY_FOR_PARENT,含 preflight_verifier) +- 负例:WRITESET_VERIFIER=/nonexistent → preflight_verifier fail / exit 2 +- 期望推导:fc5955→4/38/31、0d982→1/2/9、0fea→4/38/31;不存在的快照 → exit 1 +- 宿主 SIGKILL 能力实测(scratch 容器,非本栈):kill -9 PID1 → 策略自动重启, + RestartCount 0→1,docker events 记 exitCode=137 + +改动范围:`scripts/library-kill-takeover.sh` +统计:1 个文件,+246/-47。 + +## 开发快照 snapshot/v0.1.226 - 2026-10-29 +目标提交:ba852f3dc20119cf9a18aebde73d217acbe6f63d + +### Fixed +- **fix(worker): 接管判据绑作业身份,同快照别作业登记不算完成** +2026-10-29T19:16Z 真实现场:作业只写完 database 阶段(resources/index/wiki/verify +仍 pending),worker 被杀、人工拉起后被判 succeeded,阶段写"已登记生效版本(仅收尾, +未重写生产)",并把生效版本归属改写成这个作业。 + +根因:classifyTakeover 只看"当前生效版本的目标快照是不是我的目标快照"。版本行主键 +由 (kind, scope, 快照) 决定(LibraryVersionID),登记是 ON CONFLICT (id) DO UPDATE、 +会改写 source_job_id —— 快照相等这一维里没有作业身份。 + +改法:effectiveOwnedBy(version, jobID, snapshotID) 一份实现,接管与基线共用; +归属别的作业 ⇒ 新的 takeoverForeignEffective,走 failLibraryTakeover:不写目标、 +不释放维护、终态失败并指明预备份(目标可能半写,释放等于谎称已恢复)。 +本作业自己的 finalize 阶段成功仍是独立证据,不被归属改写推翻。 + +验证(已跑): +- go test ./cmd/worker -run TestClassifyTakeover → PASS(含 4 条新用例) +- 负例:撤掉身份判定后同两条用例红(classify="pending_final", want "foreign_effective") +- 真实 PG(一次性库 kg_takeover_foreign_test): + TestTakeoverRefusesSameSnapshotRegisteredByAnotherJob PASS(1.41s), + 负例同一用例红(library_takeover_integration_test.go:53) +- 同一库 TestTakeoverClassificationUsesExactCurrentVersionBeyondHistoryWindow PASS +- make verify:overall success(15 项必需全绿) + +改动范围:`cmd/worker/library_baseline.go`、`cmd/worker/library_finish.go`、`cmd/worker/library_handlers.go`、`scripts/test-scope.json`、`cmd/worker/library_finish_test.go`、`cmd/worker/library_takeover_integration_test.go` +统计:6 个文件,+136/-9。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_finish_test.go`、`cmd/worker/library_takeover_integration_test.go` + +## 开发快照 snapshot/v0.1.225 - 2026-10-29 +目标提交:a19cf819fc4284e722129c52701fbd3ae45b355d + +### Fixed +- **fix(scripts): 修演练脚本的撤注入与 restore 自锁** +两处实测出来的缺陷: + +- --restore 也走完整前置体检,而体检里"不许已经带注入"这一条会把它自己锁死 + (上一轮没收尾时它就永远跑不了)。restore 只校验 docker/worker/归档根。 +- 注入用的 overlay 被 compose 记进容器的 config_files 标签,restore 时原样重放 + 等于把注入又加回去(实测 KG_TEST_BARRIER_DIR 撤不掉)。按文件名排除演练 overlay。 + +另外演练中断(not_run/接管失败)时明确提示"屏障仍在注入,先 --restore", +不让父以为栈已经干净。 + +改动范围:`scripts/library-kill-takeover.sh` +统计:1 个文件,+19/-1。 + +## 开发快照 snapshot/v0.1.224 - 2026-10-29 +目标提交:465cae80bd00a70d054036e6bef68862a3ff8818 + +### Docs +- **docs: 记录"窗口当索引"陷阱与本次改动** +- **"当前生效版本"靠最近 50 条找,历史一长就把已生效读成没有**:接管分类 + (`classifyTakeover`)、基线判定(`classifyBaselineVersions`)与"已知索引映射" + 都把分页窗口当索引;窗口之外的生效版本被读成"不存在"之后,接管会把已经上线的 + 目标重写一遍,基线入口会为已有权威版本的目标再建一次基线。现在统一走两条精确 + 查询:`LibraryCurrentVersion`(`kind='production_effective' AND superseded_at IS NULL`, + 每 scope 至多一行)与 `LibraryVersionOfSnapshot`(按快照身份取回"已顶替"证据)。 + 同一含义只保留一份实现:同步与恢复共用 `knownIndexMapping`。 +- **同一判断在接口侧也要改,不是"只是展示"**:整库页的生产版本、生产同步闸口 + (这是**创建**判据:读错会放行一次不必要的覆盖)、建立基线入口(读错会覆盖 + 别人的版本)、归档保留里"生产生效版本"的保护标记,全部改用精确查询;查询失败 + 一律 fail closed(500/拒绝),不猜。历史列表仍是分页展示,`LibraryVersions` + 的 1..100 上限没动。 + +- **真实 worker 中断演练的固定同步屏障**(`internal/testbarrier`,默认关闭): + 只有 `KG_TEST_BARRIER_DIR` 与 `KG_TEST_BARRIER_POINTS` 同时在场才生效,且只在 + 本机测试栈通过 compose overlay 注入。worker 每个阶段写完后写现场文件并停在固定 + 点位,演练方看到它再 SIGKILL——命中是确定的,不是靠 sleep 猜时间。进程被杀后 + 重启的 worker 看到同一份现场文件直接通过,不会把接管再拦住。 +- `scripts/library-kill-takeover.sh`:前置体检(目标拓扑、无在飞作业、无未释放 + 维护窗口、镜像真的带屏障代码)→ 注入屏障 → 等命中 → 锁住这一个作业 → 读回写 + 集合台账并独立校验摘要 → SIGKILL 容器 → 等同一作业被接管 → 校对目标内容、 + 维护释放与服务恢复 → 撤掉注入。只有整条链路成立才 exit 0;missed / 超时 / + 接管失败分别用不同退出码,**"没做"与"做成"分开**。 + +改动范围:`scripts/test-scope.json`、`AGENTS.md`、`CHANGELOG.md`、`docs/agents/traps.md` +统计:4 个文件,+68/-0。 + +## 开发快照 snapshot/v0.1.223 - 2026-10-29 +目标提交:ed307f3c2014f5896c2580795402e32db1af5453 + +### Changed +- **feat(scripts): 真中断演练脚本改成父可直接执行** +旧 kill-takeover.sh 的判据自相矛盾:要求"作业已 running 且 ledger 齐"才启动, +可它又要先启动再等作业;维护窗口里要求业务 health 200(那时 app 是被有意停掉 +的);没杀到任何进程也 exit 0。 + +这一版把"做成"定义清楚:真 SIGKILL 真容器 → 同一作业被接管 → 目标按期望内容 +恢复 → 服务/入口恢复,四条全成立才 exit 0;missed/超时/接管失败各有非零退出码 +并写成 not_run。流程:前置体检(目标拓扑、无在飞作业、无未释放维护窗口、镜像 +里真的带屏障代码、只允许本机栈)→ 注入屏障 → ARMED 后由人在页面发起作业 → +看到 .hit 锁住这一个作业 → 读回写集合台账(文件名/自述摘要/作业绑定,可选独立 +校验器)→ SIGKILL → 等同一作业接管 → 核对 prebackup:skipped、生效版本、写集合 +摘要不变、目标计数、维护释放与容器恢复 → 撤掉注入。每一步有界,不做后台 sleep +轮询假装工作。 + +改动范围:`scripts/library-kill-takeover.sh` +统计:1 个文件,+544/-0。 + +## 开发快照 snapshot/v0.1.222 - 2026-10-29 +目标提交:0d3a016a9e06657088a82c591f3ba60238cafee1 + +### Changed +- **feat(worker): 加真实中断演练的固定同步屏障** +真杀 worker 只有在"写了一半"那一刻才有意义,靠 sleep 猜时间会错过阶段 —— +上一版演练脚本就是这样永远 exit 0 的。这里把"进程确实停在固定点"变成文件事实: +worker 到达点位后写 /.hit 再等放行,演练方看到它再 SIGKILL,命中 +是确定的。 + +安全边界:只有 KG_TEST_BARRIER_DIR 与 KG_TEST_BARRIER_POINTS 同时在场才生效 +(默认关闭),只由本机 compose overlay 注入;单次等待有硬超时,没人放行就自己 +继续;同一进程被杀后重启看到同一份 .hit 直接通过,不会把接管再拦住。屏障挂在 +contentTrackedTarget(阶段之间),发布链路不需要知道演练的存在。 + +验证:屏障包 4 条用例(默认关闭无副作用 / 命中与放行握手 / 超时自行继续并留 +.hit+.timeout / 目录不可建报错但不阻塞)与包装层接线用例全绿(后者证明真实 +Apply 路径真的走到点位,漏接线会红)。 + +改动范围:`internal/testbarrier/barrier.go`、`cmd/worker/library_barrier_test.go`、`internal/testbarrier/barrier_test.go` +统计:3 个文件,+366/-0。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_barrier_test.go`、`internal/testbarrier/barrier_test.go` + +## 开发快照 snapshot/v0.1.221 - 2026-10-29 +目标提交:0088004cd6cec924092a39de63b95c0b570d7045 + +### Fixed +- **fix(library): 当前生效版本改按精确查询** +接管分类、基线判定、已知索引映射、同步闸口的创建判据与归档保留的保护标记, +原来都用 LibraryVersions(scope, 50/100) 这个分页窗口再在内存里筛。生效版本每 +scope 只有一行、可能很久以前登记,历史一长就被挤出窗口 —— 于是"已生效"被读成 +"没有生效":接管会把已经上线的目标重写一遍,基线入口会给已有权威版本的目标 +再建一次基线。 + +现在统一走两条精确查询:LibraryCurrentVersion(kind='production_effective' AND +superseded_at IS NULL,每 scope 至多一行)与 LibraryVersionOfSnapshot(按快照 +身份取回"已顶替"证据)。多行、自相矛盾、读不到一律 fail closed;同一含义只留 +一份实现(knownIndexMapping 同步与恢复共用)。LibraryVersions 仍是 1..100 的 +展示窗口,只用于列表。 + +验证:真实 PG 上先复现(窗口实现 ⇒ found=false),改后 7 条 store 契约用例与 +worker 端到端判定用例全绿;本机 make verify 15 项 success(用例 1264 通过 / +0 失败 / 93 已声明可选跳过)。 + +改动范围:`cmd/worker/library_baseline.go`、`cmd/worker/library_finish.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go`、`cmd/worker/library_versions.go`、`internal/httpapi/library_baseline.go` 等 15 个 +统计:15 个文件,+655/-130。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_baseline_test.go`、`cmd/worker/library_finish_test.go`、`cmd/worker/library_takeover_integration_test.go`、`internal/httpapi/library_baseline_test.go` 等 6 个 + +## 开发快照 snapshot/v0.1.220 - 2026-10-29 +目标提交:67743f22854d4bc378be7030ac30aa72f196e07b + +### Tests +- **test(library): 真实PG验证超出历史窗口仍能取回写集合** +fake 版本库不实现 Store 的 1..100 上限,正是它让"限 out of range"一路绿到线上。 +这组用例跑真实 PostgreSQL:真实 Run → contentTrackedTarget → 真实 +libraryStageRecorder.WriteSetPlanned → 真实 store,断言台账在第一笔写入之前可读、 +原备份证据字段与 writeset_digest 原子共存,并把记录推到 250 条更新版本之后 +(窗口里已看不到它)仍能按身份取回。 + +store 级契约用例覆盖:命中、真缺失 found=false、别的作业=冲突、同身份两行=拒绝挑选、 +跨 scope/跨 kind 不命中、不完整身份拒绝。变异成"列最近 N 条再筛"时四条全红。 +隔离 schema 逻辑收敛到 pgtest 一份,postgres 侧改为委托。 + +改动范围:`internal/platform/postgres/pgtest/isolated.go`、`scripts/test-scope.json`、`cmd/worker/library_writeset_integration_test.go`、`cmd/worker/library_writeset_test.go`、`internal/platform/postgres/integration_isolation_test.go`、`internal/platform/postgres/library_version_lookup_integration_test.go` +统计:6 个文件,+461/-37。 + +## 开发快照 snapshot/v0.1.219 - 2026-10-29 +目标提交:f0bd6fb0d250aa713767638a973c3916c530008f + +### Fixed +- **fix(library): 版本台账改按身份精确查询** +写集合台账的取回走的是"按 scope 列最近 N 条再筛",而真实 Store 只接受 1..100, +于是真作业在预备份已落盘之后登记失败(library version limit out of range), +写集合引用没能持久化。假版本库没有这个上限,单测因此一路绿。 + +只把 200 改成 100 不够:历史超过窗口后,存在会被读成不存在(本机实测:把记录推到 +250 条更新版本之后,接管/恢复就找不到它)。改为 Store.LibraryVersionByRef: +scope+kind+作业+快照 一条 SQL 精确命中;命中多行拒绝挑选,命中别的作业拒绝借用, +真的没有才返回 found=false。登记/接管/恢复三处共用它,公开分页上限保持 1..100 不变。 +版本行解码抽成 scanLibraryVersions,窗口列表与精确查询共用一份。 + +改动范围:`cmd/worker/library_writeset.go`、`internal/platform/postgres/library_jobs.go` +统计:2 个文件,+91/-34。 + +## 开发快照 snapshot/v0.1.218 - 2026-10-29 +目标提交:27828ff21e7c66846ba41ed50894814915fdf36f + +### Tests +- **test(worker): 编排链路里包装层必须真的触发写集合预检** +只测“包装体转发”还不够:Run 是按可选接口断言决定要不要预检的。补一条走 +libraryrelease.Run + contentTrackedTarget 的用例,断言预检被调用一次、结果 +带回编排层、四类写入照常执行。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`cmd/worker/library_writeset_test.go` +统计:1 个文件,+59/-0。 + +## 开发快照 snapshot/v0.1.217 - 2026-10-29 +目标提交:18683ca63ca10dc8238d230eeb677608e911dd81 + +### Docs +- **docs(agents): 记录资源范围冲突与可审计复用判据** +trap-16:resources 是多知识库共享表,“不在预备份范围”不等于“属于别的 +知识库”;恢复保留下来的行是本目标上次成功同步的结果。判据、修法与反例 +写进全文,索引表加一行。 + +仅文档变更,不改变运行时行为。 + +改动范围:`AGENTS.md`、`docs/agents/traps.md` +统计:2 个文件,+28/-0。 + +## 开发快照 snapshot/v0.1.216 - 2026-10-29 +目标提交:0252492e53b76c48c2deca86f5c497c055ba73ac + +### Fixed +- **fix(library): 恢复保留下来的资源凭可审计证据复用** +同步 A 成功后手工恢复 B,resources 表既不删行也不删对象;再同步 A 时, +resources 里那 21 行以“同租户但不在预备份范围”被判成别人的知识库,整次 +同步在写第一笔之前被拒。 + +“目标里没人引用”不是归属证据:历史孤儿、其它知识库暂未绑定的资源都满足 +它。改成唯一放行路径——可审计复用(v071/library_reuse.go): +1. 持久来源:这个目标成功同步过且登记过生效版本的快照,或它自己的预备份, + 且同一资源行在来源里内容一致; +2. 整行内容(除易变时间戳)与本次写入将要落地的行完全一致; +3. 该资源的对象字节与本次快照归档摘要一致。 + +三条同时成立才允许复用:写入阶段跳过它(不重写行、不重传对象),写集合里 +记 existing+reused,恢复不清理;证据单独落盘 reuse/.json,摘要写进 +写集合行,不冒充历史台账。内容不同、来源范围不同、或没有持久来源一律继续 +拒绝,且拒绝发生在写入之前。 + +为什么把预备份也算来源:恢复作业的写集合同样会把“本目标历史状态里已有的 +资源”判成跨范围冲突,而它们本来就出自这个目标自己的快照。 + +验证:隔离 PG+ES+MinIO 真服务下新增 5 个场景用例(复用成功且 xmin 不变、 +无来源拒绝、内容不同拒绝、同内容不同来源拒绝、wiki 半写回滚只清本次新建); +v071 整包与全仓 go test 通过;make verify 15 项全绿。 + +改动范围:`cmd/worker/library_handlers.go`、`cmd/worker/library_writeset.go`、`internal/libraryrelease/release.go`、`internal/libraryrelease/writeset.go`、`internal/platform/postgres/library_jobs.go`、`internal/releaseadapter/v071/library_production_target.go` 等 12 个 +统计:12 个文件,+1034/-18。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_writeset_test.go`、`internal/libraryrelease/writeset_test.go`、`internal/releaseadapter/v071/library_reuse_integration_test.go`、`internal/releaseadapter/v071/library_writeset_integration_test.go` + +## 开发快照 snapshot/v0.1.215 - 2026-10-29 +目标提交:cf3f4662ccb88d6b6bf640749e628ec16017ccab + +### Docs +- **docs: 记录写集合与定位符陷阱并归档真作业证据** +- **写集合台账在真作业里根本没生成**:worker 把目标包成 `contentTrackedTarget` + 以标记"内容可能半写",但包装层没转发 `PlanWriteSet`,而 orchestrator 是用 + **类型断言** `target.(WriteSetPlanner)` 决定要不要算写集合的 —— 断言恒为假, + 台账既不落盘也不登记。真实后果:10-29 两次真作业的预备份证据里都没有 + `writeset_digest`,归档根下没有 `writesets/`,一旦 worker 崩在写入中间接管必然 + 失败。现在包装层显式转发;内层不支持预检时返回硬错误,不退回"没有台账也照写"。 +- **`S3ObjectStore.Stat`/`Delete`/`Copy` 与 `Open`/`Put` 吃不同的定位符**:真实资源 + 路径是 `storage:///:///`,前三个用只认单层的 + `validObjectLocator` 并把整串当对象键,于是"对象在桶里但校验读不到"。现在四个 + 入口共用 `validObjectKey` + `providerPathFromLocator`。集成用例改成喂真实定位符, + 负例覆盖键不存在 / provider 不符 / 路径穿越 / 复制源不存在;写集合集成用例里 + 那条"读不到也当通过"的容忍断言改为真断言。 +- **已成功/已失败的作业在页面上仍写"尚未启动""此时尚未运行"**:生产版本行与作业条 + 的文案原来写死,阶段条只认 `kind=sync`。现在按作业类型与终态显示,恢复/基线也 + 用同一串阶段读自己的阶段行(不再一律"本次作业尚未开始")。 +- **预备份已登记却显示"尚未创建/无记录"**:`overview.prebackup` 原来只认"与拟发布 + 快照同内容的那一份",而恢复作业登记的是**恢复前**的备份,两者必然不同。现在显示 + 最近一次已登记预备份,并给出所属作业与"是否为当前拟发布目标";闸口判定不变。 +- **所有归档都被标成"测试封存"**:归档根下同时有目标基线与每次预备份。overview 新增 + 轻量版本清单(不含 evidence),抽屉按登记过的 kind 标注每份快照,未登记的归档 + 如实写"未登记(归档文件,不作为发布候选)"。 +- **基线与恢复被写成"生产同步"/"封存进行中"**:作业类型改成逐类映射 + (测试封存 / 生产同步 / 按版本恢复 / 建立目标基线),页签徽标、作业头、明细表同步。 +- **轮询只覆盖封存队列**:点了同步/恢复/建立基线之后页面根本不刷新,看上去像"还没 + 启动",而失败作业又可能被无限轮询。现在轮询覆盖"投递 → 终态读回":投递中的动作、 + 封存队列作业、任一 scope 的在飞持久作业都算在跑;读到终态(成功/失败/取消)再读 + 回一次就停。 +- **维护窗口的起始时间永远是第一次建立基线那一刻**:`acquired_at` 没进 + `ON CONFLICT DO UPDATE`,新作业接管已释放的窗口时不会换时间。库级与实例级两份 + SQL 都改成"同一作业重入才保留旧值,新作业接管用本次时间"。 + +- `docs/agents/traps.md` trap-14(包装层丢掉可选接口 ⇒ 台账静默跳过)、 + trap-15(对象存储各入口定位符形状不一致)。 +- 真实 PostgreSQL 用例:维护窗口起始时间只属于当前这次占用。 +- 真实 MinIO 用例:正例用 `storage://` 定位符走 Put/Open/Stat/Copy/Delete,负例四种。 + +改动范围:`AGENTS.md`、`CHANGELOG.md`、`docs/agents/traps.md`、`docs/evidence/2026-10-30-real-sync-restore/README.md`、`docs/evidence/2026-10-30-real-sync-restore/archive-integrity.txt`、`docs/evidence/2026-10-30-real-sync-restore/jobs.txt` 等 11 个 +统计:11 个文件,+480/-1。 + +## 开发快照 snapshot/v0.1.214 - 2026-10-29 +目标提交:74fb04b2d1e50b1f227a19d0c0582394beff4f4f + +### Fixed +- **fix(web): 整库页按作业类型与终态显示并补全轮询** +- 成功的作业不再写"尚未启动/此时尚未运行",阶段条对恢复/基线也读自己的阶段行; +- 基线/恢复不再被写成"生产同步""封存进行中",改为逐类映射; +- 抽屉按版本登记标注每份归档,未登记的不冒充候选; +- 轮询覆盖投递→终态读回:投递中动作、封存队列、任一 scope 在飞作业都算在跑, + 读到终态再读回一次就停,失败作业不会被无限轮询。 + +改动范围:`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts` +统计:3 个文件,+498/-51。 + +## 开发快照 snapshot/v0.1.213 - 2026-10-29 +目标提交:402173263ecc2e18c2f216ead0b397b7c79f5d6e + +### Fixed +- **fix(library): 预备份与归档类型按版本登记显示** +两处契约错:prebackup 只认"与拟发布快照同内容的那一份",而恢复作业登记的是 +恢复前备份(必然不同),于是刚备份完就显示"尚未创建";归档里同时有目标基线与 +每次预备份,overview 不给登记类型,页面只能把每份都写成"测试封存"。 + +现在 prebackup 取最近一次已登记的那份,并给出所属作业与 bound_to_target; +overview 新增不含 evidence 的轻量版本清单供页面标注每份快照。闸口判定不变。 + +改动范围:`internal/httpapi/library_flow.go`、`internal/httpapi/library_flow_test.go` +统计:2 个文件,+105/-14。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_flow_test.go` + +## 开发快照 snapshot/v0.1.212 - 2026-10-29 +目标提交:72ea9726e7073d5c4cbb8b459cfef0cd1c48e38d + +### Fixed +- **fix(library): 维护窗口起始时间只属于本次占用** +acquired_at 没进 ON CONFLICT DO UPDATE,于是第一次建立基线写下的时间再也不会变: +恢复作业 16:59 才开窗口,页面却显示 13:37。库级与实例级是两份 SQL,都要改成 +"同一作业重入(崩溃接管)保留旧值,新作业接管用本次时间"。 + +真实 PostgreSQL 用例覆盖两者:首开、同作业重入、释放后新作业接管、持有期间他人被拒。 + +改动范围:`internal/platform/postgres/library_instance_maintenance.go`、`internal/platform/postgres/library_maintenance.go`、`internal/platform/postgres/library_maintenance_window_integration_test.go` +统计:3 个文件,+121/-0。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/library_maintenance_window_integration_test.go` + +## 开发快照 snapshot/v0.1.211 - 2026-10-29 +目标提交:168fc2ecb7427140233825460c3a9897b4677ed3 + +### Fixed +- **fix(release): 对象存储四个入口共用同一定位符解析** +真实资源路径是 storage:///:///。Open/Put 用 +validObjectKey 并拆出裸 key,而 Stat/Delete/Copy 用只认单层的 validObjectLocator +且把整串当对象键——对象在桶里却校验读不到。集成用例只喂裸 key,把这条不一致 +藏了过去;写集合用例里那句"读不到也算通过"同样把断言关掉了。 + +现在四个入口共用 validObjectKey + providerPathFromLocator;集成用例喂真实定位符, +负例覆盖键不存在 / provider 不符 / 路径穿越 / 复制源不存在,并验证 Delete 后再读必失败。 + +改动范围:`internal/releaseadapter/v071/s3_object_store.go`、`internal/releaseadapter/v071/library_writeset_integration_test.go`、`internal/releaseadapter/v071/s3_object_store_integration_test.go` +统计:3 个文件,+108/-28。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_writeset_integration_test.go`、`internal/releaseadapter/v071/s3_object_store_integration_test.go` + +## 开发快照 snapshot/v0.1.210 - 2026-10-29 +目标提交:f0f5571518bd644429da4fa3148219da2e0e1042 + +### Fixed +- **fix(worker): 写集合预检经包装层转发,缺失即拒写** +orchestrator 用 target.(WriteSetPlanner) 决定要不要在写第一笔之前算写集合, +而 contentTrackedTarget 只转发了 Current/Apply/Verify/Restore,断言恒为假, +台账既不落盘也不登记。真实现场:10-29 两次真作业的预备份证据里没有 +writeset_digest,归档根下没有 writesets/,崩溃接管必然无台账可读。 + +现在包装层显式转发;内层不支持预检时返回硬错误,不退回"没有台账也照写"。 +用例断言包装后仍实现 WriteSetPlanner,并覆盖"内层不支持"的硬错误。 + +改动范围:`cmd/worker/library_handlers.go`、`cmd/worker/library_writeset_test.go` +统计:2 个文件,+65/-0。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_writeset_test.go` + +## 开发快照 snapshot/v0.1.209 - 2026-10-29 +目标提交:a0a7a266394557e338860eb8b272349ee44bfc4c + +### Tests +- **test(library): 写集合锚点与接管加载的单测** +治理侧的摘要登记与取回原先只能连真实治理库验证,等于没有负例。这里把"列版本" +抽成一个最小接口(真实 store 原样满足),并为三个判据补单测:预备份原有证据必须 +保留、摘要必须按"作业+目标+预备份"三者匹配才能取回、接管时没有写集合必须失败。 + +本地验证:go test ./cmd/worker/ -run 'WriteSet|LedgerBackup' 4 项通过; +go vet ./cmd/worker/ 无输出。 + +改动范围:`cmd/worker/library_writeset.go`、`cmd/worker/library_writeset_test.go` +统计:2 个文件,+177/-17。 + +## 开发快照 snapshot/v0.1.208 - 2026-10-29 +目标提交:e0768f2caadb3359b680586b9355157f20987062 + +### Fixed +- **fix(library): 写集合台账与跨范围冲突预检** +失败恢复会在写完之后按"目标租户里没人引用"清理资源行,于是历史孤儿、同租户其它 +知识库暂未绑定的资源、被共享的资源都可能被删掉;而预备份里没有这些行,删了不可 +恢复,随后整实例指纹才发现不一致,只能停在 rollback_failed。恢复校验依赖的整实例 +证据又只存在内存里,worker 重启后只按快照 id 恢复,等于静默跳过比对。 + +改法:写第一笔之前把"本次明确写集合 + 写前存在状态 + 预备份时刻整实例内容证据" +算出来并按内容寻址落盘(文件名与治理库里的引用都是 sha256),摘要登记在预备份 +版本证据里;恢复只删"写前不存在 ∧ 由本次写入 ∧ 不在恢复内容里"的精确 ID,并对 +资源额外要求仍无绑定/正文引用;缺记录、记录被改、绑定对不上都直接失败、维护保持。 +resources 的 upsert 补租户限定,跨范围同 ID 在写入之前就被预检拒绝。 +整实例指纹从"身份集合"改成"逐行内容摘要",别的知识库同 ID 行被覆盖写坏也会暴露。 + +本地验证(已跑): +- make verify 15 项全 success(identity 因本地档案 skipped) +- go test ./internal/releaseadapter/v071/ -run Isolated(真实 PG + 真实 ES + + 真实 MinIO 对象存储,写失败按定位符注入):目标非空/同租户其它知识库/共享资源/ + 历史孤儿/跨租户数据在失败恢复后逐行字节不变、本次残留 0;缺写集合拒绝恢复; + 跨范围 ID 冲突在写入前拒绝;wiki 阶段半写回滚干净 +- SIGKILL 子进程后按盘上写集合接管恢复,目标原有数据逐行不变 +- go test ./internal/libraryrelease/:写集合先于第一笔、规划失败/登记失败都不写目标、 + 接管不重新规划 + +改动范围:`cmd/librarydrill/main.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go`、`cmd/worker/library_writeset.go`、`internal/libraryrelease/release.go`、`internal/libraryrelease/writeset.go` 等 16 个 +统计:16 个文件,+2103/-99。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/release_test.go`、`internal/libraryrelease/writeset_test.go`、`internal/releaseadapter/v071/library_export_integration_test.go`、`internal/releaseadapter/v071/library_production_target_integration_test.go` 等 6 个 + +## 开发快照 snapshot/v0.1.207 - 2026-10-29 +目标提交:034c0b3a1c0b9fc5884750cc0d9929c0df4ac314 + +### Changed +- **Merge pull request 'fix(library): 跨源同步按目标范围改写并校验残留' (#13) from fix/library-cross-scope-sync-residue into main** +该提交未附正文,以下范围由真实 diff 归纳。 + +合并提交:以上仅按第一父提交的 diff 边界归纳,不把并行分支改动重复计入。 + +## 开发快照 snapshot/v0.1.206 - 2026-10-29 +目标提交:5defe238e0b992ab4d0037f63d04606d9e16cd2c + +### Fixed +- **fix(library): 跨范围同步按目标范围改写并校验残留** +真实事故:源 KB(10000/0c84a098) 同步到目标 KB(10001/e64e82b5) 时,归档行被原样 +插入目标库,落在来源租户/知识库;资源阶段随即报"生产缺少资源 ... 的存储路径" +(资源查询按目标租户过滤,看不见刚写进去的行)。回滚只按目标范围回放,于是页面 +报"已恢复到预备份",目标却多出 4 文件/38 分块/21 资源/4 绑定。 + +改: +- 写入与校验共用 retargetRow:tenant_id / knowledge_base_id / 资源定位符(后端、 + provider、桶、租户前缀) / storage_backend_id / provider / embedding_model_id + 统一映射到目标范围;行里的来源身份对不上就失败,宁可不写。 +- Verify 增加"本次写集合仍以来源身份残留"判定;Restore 增加整库指纹比对(预备份 + 时刻 vs 恢复后)与孤儿资源清理,回滚后用 Verify 复核,恢复工具退出 0 不再等于 + 恢复完成。 +- 新增 cmd/libraryresidue:按归档身份精确、可备份、可复核地清理已知的范围外残留。 + +验证:make verify 全绿;真实 PG+ES 用例复现"源目标 KB 不同 + 目标非空 + 资源阶段 +失败",断言回滚后无来源范围残留、其它租户行未动、恢复后内容=预备份;正向用例断言 +行落在目标范围且资源定位符指向目标存储。 + +改动范围:`cmd/libraryresidue/main.go`、`internal/libraryrelease/release.go`、`internal/releaseadapter/v071/library_backup_store.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/library_residue.go`、`internal/releaseadapter/v071/library_scope_remap.go` 等 11 个 +统计:11 个文件,+1588/-70。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/release_test.go`、`internal/releaseadapter/v071/library_production_target_integration_test.go`、`internal/releaseadapter/v071/library_scope_remap_test.go`、`internal/releaseadapter/v071/resource_locator_test.go` + +## 开发快照 snapshot/v0.1.205 - 2026-10-29 +目标提交:c27a0c14cc0a2a34f61b80e4a2d40994ba44ae2e + +### Fixed +- **fix(library): 基线契约读目标真实 schema/模型,演练标内容风险** +- **基线契约按"能验证的来源"填充**:归档要求完整的兼容性契约(v0.7.1 / schema 74 / + elasticsearch_v8 / 1024 维 / 映射摘要)。原先只填了 embedding 与映射,捕获会被 + `unsupported snapshot compatibility contract` 拒绝。现在 schema 版本**读目标库** + `schema_migrations`(dirty 或版本不符直接拒绝),embedding 优先读目标库模型绑定、 + 读不到才回落部署声明,映射取目标当前值,版本/引擎用受支持栈常量。 + +改动范围:`cmd/librarydrill/main.go`、`internal/librarystack/stack.go`、`CHANGELOG.md` +统计:3 个文件,+109/-14。 + +## 开发快照 snapshot/v0.1.204 - 2026-10-29 +目标提交:a42b101a7b50cadd70b9e369c32aaeb0311712ae + +### Fixed +- **fix(library): 基线崩溃续作与维护原状态保留** +- **基线作业"登记成功但没来得及收尾"不再被判死**:原来崩溃重入会在 + "已有生效版本"检查处直接失败,把目标留在维护里、占用也不释放。 + 现在按「本作业 + 同一快照」识别为**自己的**版本:只做收尾(恢复服务、开入口、 + 释放两把占用),不重新捕获、不重写版本;别人登记的生效版本仍然一律拒绝。 + 捕获完成但登记失败的作业会明确失败并写"未对目标写入",不会假装成功。 +- **同一作业重入不再覆盖第一次记下的原运行状态**:崩溃接管时目标已被停住, + 再读一次只会读到 `stopped`,写回去会让恢复阶段以为写入者本来就没跑、 + 永远不启动它。现在 `AcquireInstanceMaintenance` / `AcquireLibraryMaintenance` + 在 owner 相同的重入时保留已持久化的原状态;新作业接管已释放窗口仍用自己读到的值。 + (真实 PostgreSQL 集成用例覆盖,非内存假实现。) +- **基线的版本登记移进维护窗口**:捕获、校验、登记三件事要么一起成立、要么目标 + 原样恢复;记录失败时窗口已按原状态开放,治理记录同步释放,**不再出现 + "显示保持维护、实际入口已开"**。 +- **索引映射读不到时拒绝登记**:不再 `_` 忽略错误后用空摘要写进基线证据。 +- 基线窗口的统一收尾抽成 `settleBaselineWindow`:版本已登记走与同步相同的成功边界; + 窗口仍 held 才标 `needs_restore`;窗口已恢复则释放占用并失败。 + +- **`cmd/librarydrill`:目标实例真实恢复演练**(维护隔离 → 整库预备份 → 一致性校验 + → canary 漂移检测 → 按预备份恢复 → 逐类复核 → 按原状态恢复服务)。它使用与 + worker 完全相同的 `LIBRARY_*` 配置与维护协调器,绝不触碰封存源;任一步失败都 + 保持维护并写报告,不伪造通过。 +- `internal/librarystack`:目标/预备份/维护栈的唯一装配点(同步、恢复、基线、演练共用)。 + +- 基线作业的"已有生效版本"判定改为三种状态(own / foreign / none),并在 + 阶段里写明是否走"只收尾"续作。 + +改动范围:`cmd/librarydrill/main.go`、`cmd/worker/library_baseline.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_wiring.go`、`internal/httpapi/library_baseline.go`、`internal/httpapi/server.go` 等 15 个 +统计:15 个文件,+1103/-302。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_baseline_test.go`、`cmd/worker/library_finish_test.go`、`internal/httpapi/library_baseline_mutex_test.go`、`internal/platform/postgres/library_job_mutex_integration_test.go` 等 5 个 + +## 开发快照 snapshot/v0.1.203 - 2026-10-29 +目标提交:3284658eb1d5d64de8693f83732fb4ed3030293f + +### Changed +- **feat(web): 发布页接上签发同步授权与建立基线入口** +- **管理员在发布页签发同步授权(`POST /api/v1/library/authorizations`,`action=sync`)**: + 授权把「范围 + 目标快照 + 参数摘要 + 有效期」钉死,仍与执行分成两步 —— + 页面只提供入口,不会替你签发,也不会自动重试执行。工程师看得到缺项原因, + 但拿不到签发按钮(接口本来就只认管理员)。 +- **目标权威基线(迁移 075,作业类型 `baseline`)**:目标非空、又没有生效版本时, + 首次发布的真实解法是「维护隔离 → 整库捕获 → 逐类校验 → 登记基线」。 + `POST /api/v1/library/baselines` 只建作业;worker 用与同步同一套维护窗口 + (关入口、停写入者、证明排空)把目标整库捕获成归档快照,再用同一套 `Verify` + 证明目标与这份快照一致,最后登记 `production_effective`(证据写明 + `origin=target_baseline`、快照 ID、逐表行数、目标索引映射)。 + 它**不写目标内容、不清空目标、不手写版本**:没有捕获与校验证据就没有基线。 + 基线作业与 sync/restore 共用同一把写互斥(同一 scope 只允许一个在飞写作业), + worker 被杀时同一作业 id 会接管那个窗口继续,而不是把目标留在停机状态。 +- **维护能力(`GET /api/v1/library/maintenance` 的 `capability`)**:页面区分 + 「此刻没有维护窗口」与「维护能力没接线」,逐项列出缺少的 + `LIBRARY_TARGET_*`;接口没给能力信息时显示"未知",不再用一句 + "也没有维护能力配置"糊过去。 + +- **`productionScopeEmpty` 改成整库口径**:原来只数 `knowledges`,"文档删了但 + 分块/Wiki/资源还在"的目标会被判成空库,从而绕过基线初始化。现在用适配层 + 同一份表清单与范围过滤(`v071.ScopeContentEmpty`)逐表计数,并把逐表行数 + 交回接口,"空"只有一份定义。 +- **拟发布的测试封存只认版本登记(`kind=test_sealed`)**:归档里同时存在生产 + 预备份与目标基线快照,按"这个 scope 最新一份"挑会把目标自己的内容显示成 + 测试候选。现在候选来自登记、再用归档确认它还在本机;不在就说明原因。 +- **发布页不再说"生产同步执行链尚未接线"**:同步按钮按真实闸口启用, + 点击走 `POST /api/v1/library/syncs`;被拒时逐条显示接口返回的缺项。 +- 缺项文案从"生产当前权威版本未知,不能启动写入"改为指向真实入口: + 「目标非空且没有生效版本记录:先建立目标权威基线…或确认目标确实是空库」。 + +- 维护卡片把"没有窗口记录"错误地写成"没有维护能力配置",与实际接线不符。 +- 首次发布时 `POST /library/baselines` 在目标读不到时**不建作业、不登记版本** + (拒绝用建基线掩盖连接/配置故障)。 + +改动范围:`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts`、`web/src/librarySyncAuth.test.ts`、`CHANGELOG.md` +统计:5 个文件,+460/-13。 + +## 开发快照 snapshot/v0.1.202 - 2026-10-29 +目标提交:517d5f862df92230c494ae3bdeddd06021810a9e + +### Changed +- **feat(library): 首次发布接上目标权威基线与整库空判定** +目标非空又没有生效版本时,首次发布以前只能停在"权威版本未知"。现在有了 +真实入口:管理员发起 baseline 作业,worker 在维护隔离下把目标整库捕获成 +归档快照,再用同一套 Verify 证明目标与快照逐类一致,最后登记 +production_effective(证据写明 origin=target_baseline、快照 ID、逐表行数、 +目标索引映射)。不写目标内容、不清空目标、不手写版本。 + +同时修两处会误导人的判定: +- productionScopeEmpty 原来只数 knowledges,"文档删了但分块/Wiki/资源还在" + 的目标会被当成空库;改用适配层同一份表清单与范围过滤逐表计数。 +- 概览里的"测试封存候选"原来取该 scope 最新一份归档,而归档里还有生产 + 预备份与目标基线快照;改为只认版本登记里 kind=test_sealed 的那一份。 + +迁移 075 放开 kind='baseline' 并把它并入 sync/restore 的写互斥索引; +worker 重启时用同一作业 id 接管未完成的基线窗口。维护接口补 capability, +页面才能区分"未运行维护"与"维护能力没接线"。 + +验证:go build ./... / go vet ./... 通过;新增用例覆盖管理员校验、已有生效 +版本拒绝、目标读不到时零作业零版本、候选来自登记、缺项文案指向基线入口、 +capability 逐项缺失;反向自检(把候选改回按最新归档取、把文案改回旧句、 +去掉管理员判断)三项均按预期变红后复原。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/worker/library_baseline.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_wiring.go`、`cmd/worker/main.go` 等 16 个 +统计:16 个文件,+903/-59。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_baseline_test.go`、`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.201 - 2026-10-29 +目标提交:f9c15a8f14353865258fd1c4bba19c9df5f621df + +### Fixed +- **fix(web): 存储提示改为实际能力** +页面仍写"保留策略与清理在系统设置→存储管理,均未接线",但存储与备份已有只读 +去重/保护范围/预览。改为说明可查看什么、什么尚未开放。 + +改动范围:`web/src/ReleaseTargets.tsx` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.200 - 2026-10-29 +目标提交:271f9f5f0992d03e409affe0f1fd91326fa33f5e + +### Docs +- **docs: 台账补记 0.1.198–0.1.199** +记录接管分类与维护释放幂等的真实 SHA、CI run(236)与候选栈身份注入后 +/api/v1/build-info 的读回结果。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+2/-0。 + +## 开发快照 snapshot/v0.1.199 - 2026-10-29 +目标提交:24d2546cbb99e2d5d1d8e126a1bf5d06af9ee457 + +### Fixed +- **fix(library): 接管不再把版本查询失败当成没有收尾证据** +- **接管在版本查询失败时 fail-open**:`resumed` 后版本查询出错被忽略、`FinalizeOnly` 仍为 + false,于是"已成功登记并释放窗口、只差终态"的作业会被**重新写一遍目标**。现在接管先按 + 证据分类:`finalize` 阶段成功是独立证据(不受版本查询影响);查询失败且无阶段证据 ⇒ + **未知**,零写入、作业失败可重试;本快照的生效版本已被别的快照顶替 ⇒ **拒绝覆盖**; + 只有真正没有收尾证据时才按正常流程。 +- **维护释放部分成功后的重试不再永久卡死**:`Update*/Release*` 对"已由本作业释放"的行 + 返回可判别的 `ErrMaintenanceReleased`,收尾重试视其为已完成;别的作业来释放仍是 + "未持有"错误。真 PG 负例:同作业重复 Update/Release 得到已释放,跨作业得到未持有。 + +改动范围:`cmd/worker/library_finish.go`、`cmd/worker/library_handlers.go`、`internal/platform/postgres/library_instance_maintenance.go`、`internal/platform/postgres/library_maintenance.go`、`CHANGELOG.md`、`cmd/worker/library_finish_test.go` +统计:6 个文件,+186/-41。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_finish_test.go` + +## 开发快照 snapshot/v0.1.198 - 2026-10-29 +目标提交:691e4e0225b8b2b861be80a58a134158c81603ee + +### Docs +- **docs: 台账补记 0.1.195–0.1.197** +记录 R1 身份契约、封存资源诊断与 R4 成功边界三个提交的真实 SHA 与 CI run, +标明候选栈 clean 构建(vcs.modified=false)与真实验收覆盖范围。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+4/-0。 + +## 开发快照 snapshot/v0.1.197 - 2026-10-29 +目标提交:8d2ed8ebe61fcf5ef9a5e1b25923e6456c52c5c8 + +### Fixed +- **fix(library): 维护释放与服务恢复完成之后才暴露作业成功** +- **作业成功暴露早于维护结束**:原来 `execute` 里写完生效版本就 `AdvanceLibraryJob(succeeded)`, + 而服务恢复、入口开放与维护锁释放要在窗口返回后才做。任何一步失败时外部已经看到 + `succeeded`,重启接管又只认非终态,锁就永远挂着。现在内容校验/版本登记之后只记 + "待收尾",`finishLibrarySuccess` 依次确认目标写入者与入口健康、按 **scope → instance** + 顺序释放两把锁(每一步错误都上抛,不再丢弃),最后才写 `succeeded`;任一步失败都把 + 作业落成失败、维护保持 `needs_restore`。释放与终态写入用 `context.WithoutCancel` + 包住,停机不会留下半成品。接管判定改为按证据(finalize 阶段成功或存在未顶掉的生效 + 版本)转 finalize-only,只复核与登记,不重写生产。 + +改动范围:`cmd/worker/library_finish.go`、`cmd/worker/library_handlers.go`、`CHANGELOG.md`、`cmd/worker/library_finish_test.go` +统计:4 个文件,+308/-25。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_finish_test.go` + +## 开发快照 snapshot/v0.1.196 - 2026-10-29 +目标提交:c14dd01f08ca1d7a5fccbe5ffdc0b69ed3463962 + +### Fixed +- **fix(library): 封存读资源失败带上资源行与底层原因** +- **封存读资源失败只剩一句 `read library resource`**:对象缺失、桶错、端点解析不到 + 都会走到同一个字符串,真实封存直接卡在这里无法判断方向。现在带上资源行 id 与底层 + 原因(`read library resource : `),不含凭据。 + +改动范围:`internal/releaseadapter/v071/library_archive.go`、`CHANGELOG.md` +统计:2 个文件,+7/-1。 + +## 开发快照 snapshot/v0.1.195 - 2026-10-29 +目标提交:6ed06357f9a0fa7787796beb149d053407a6b3eb + +### Fixed +- **fix(library): 期望执行器身份随 capture 请求送达并在执行前核对** +- **封存身份核对移进请求本身(TOCTOU)**:原来 worker 先 `health` 再 `capture`,两次 + 拨号之间 socket 可能被换成另一个 agent——错的执行器已经把写入者停了才被拒登记。 + 现在 `capture` 请求携带治理侧配置生成的期望身份(`project` 必填,`source_app`/ + `archive_root` 配了就一起绑定)与稳定指纹(`sha256:`,覆盖三项);接收方在**操作 + 互斥内、执行任何源操作之前**比对,缺失或被改过一律拒绝,错的执行器一次 `capture` + 都不跑。封存结果回显该指纹,worker 事后复核只作为证据防线,不能替代请求内核对。 + 恢复路径不受期望约束(它只把写入者恢复回来)。 + +改动范围:`cmd/worker/library_capture_identity.go`、`cmd/worker/main.go`、`internal/appconfig/config.go`、`internal/libraryagent/agent.go`、`internal/libraryagent/client.go`、`internal/libraryagent/expectation.go` 等 12 个 +统计:12 个文件,+382/-161。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_capture_identity_test.go`、`internal/libraryagent/agent_test.go`、`internal/libraryagent/health_test.go`、`internal/libraryagent/swap_test.go` + +## 开发快照 snapshot/v0.1.194 - 2026-10-29 +目标提交:5d42cba6c628fa32dc41caa05d3580e4d1924c45 + +### Docs +- **docs: 执行器证据绑定到确切代码 SHA 而不是版本号** +矩阵里写"0.1.187 构建"会与台账的版本号漂移;改成代码 SHA 4c79052(0.1.192 工作树), +与候选二进制来源一致。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-acceptance-matrix.md` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.193 - 2026-10-29 +目标提交:a47f9323e060074d90eb1a3db53d826711e81ba5 + +### Docs +- **docs: 台账补记执行器可用性与还原入口(0.1.188–0.1.192)** +记录真实 SHA、CI run 229(head 4c79052,必需 context success)、候选栈二进制来源与 +回退目录。代码 SHA 与已部署候选产物分开写,不把文档提交当成部署证据。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+11/-0。 + +## 开发快照 snapshot/v0.1.192 - 2026-10-29 +目标提交:4c79052a189ad2be3892498905774d77fc40a67e + +### Tests +- **test(release-worker): 测试 DSN 改为拼接构造避免密钥扫描误报** +完整带口令的 URL 出现在源码里会被 security-secrets 判红(测试值也一样)。 +按既有做法把连接串拆开拼接,语义不变。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`cmd/release-worker/diagnosis_test.go` +统计:1 个文件,+5/-4。 + +## 开发快照 snapshot/v0.1.191 - 2026-10-29 +目标提交:18276d0839ce44b4fbf36675ced45884ab379cae + +### Docs +- **docs: 按用户决定收窄整库还原范围并记录执行器证据** +- **封存执行器的运行时可用性(迁移 074 `library_executor_status`)**:页面上的 + "可以发起封存"不再等于 `LIBRARY_AGENT_SOCKET` 非空。主机侧 agent 新增 + `health` 动作(不占封存操作锁、不跑子进程),worker 每 30 秒探测一次并把 + "指向哪个实例、归档在哪、是否降级/正在跑、观测时间"落库;API 只读这份观测。 + socket 只挂在 worker 容器里,所以由 worker 观测、页面读结果,而不是让 API + 假装能 dial。观测过期、不可达、实例不符、降级、正忙时页面与 + `POST /library/captures` 用同一份判定拒绝,并逐条给出原因。 +- **封存执行前后的身份复核**:封存结果由 agent 附上自报身份(`agent_identity`)。 + worker 在停写入者之前再探测一次,执行后再用结果里的身份复核;不一致时作业失败 + 且**不登记**为测试封存——心跳只能证明"刚才",从心跳到停写入者之间 socket + 可能被换掉。 +- **按版本恢复的页面入口**:`GET /api/v1/library/restore-availability` 说明 + "这份快照现在能不能恢复"(双闸、该快照的恢复授权、目标实例是否在维护)。 + 页面据此启用"恢复至该版本",管理员可签发 30 分钟有效期的 restore 授权; + 工程师只看到条件,不获得入口。 +- 可选 `LIBRARY_SOURCE_PROJECT`:声明封存执行器应指向的实例名(默认等于 + `LIBRARY_SOURCE_INSTANCE`)。它描述封存源,与同步目标 `LIBRARY_TARGET_*` 分开。 + +- `LIBRARY_EXECUTOR_STATUS_MAX_AGE`(默认 120 秒)控制执行器观测的有效期; + 未设置不等于"永不过期",过期即"无法确认"。 +- 页面/接口不再出现写死的"封存通道未接线""生产恢复未接线":不能发起时给出真实原因、 + 执行器身份与观测时间。 + +- **正式 release-worker 启动失败无法定位**:配置里的目标库指向不存在的主机时, + 日志只有一句 `connect release target database`。现在错误里带库端点 + (`host:port/db`,去口令)与路由标识,并保留底层原因;原因文本先经 + `pgdsn.Redact` 抹掉口令(pgx 的解析错误可能原样回显连接串),可诊断不等于泄露。 + 共享的 `internal/pgdsn` 替换了 cmd/worker 里只留库名的旧实现。 +- `libraryctl` 用法文本补上 `dead-letters` 子命令。 + +改动范围:`CHANGELOG.md`、`docs/library-acceptance-matrix.md` +统计:2 个文件,+65/-5。 + +## 开发快照 snapshot/v0.1.189 - 2026-10-29 +目标提交:c40696145b43a47ea0cef6d83ae2fbe00bb3677f + +### Changed +- **feat(library-web): 恢复入口按真实条件启用并支持签发恢复授权** +原来"恢复至该版本"写死 disabled,标题还写"生产恢复未接线"——而 POST /library/restores +与授权接口早已可用,等于把整库还原的唯一入口藏起来。 + +现在页面按 GET /library/restore-availability 启用:缺授权时管理员可签发 30 分钟 +有效期的 restore 授权(固定期限,没有"永久授权"),条件满足才可点恢复;工程师只看到 +条件与"仅管理员可发起"。封存不可发起时也不再写死"通道未接线",而是显示接口原因、 +执行器身份与观测时间。 + +改动范围:`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/libraryRestore.test.ts`、`web/src/librarySnapshots.ts` +统计:4 个文件,+433/-14。 + +## 开发快照 snapshot/v0.1.190 - 2026-10-29 +目标提交:cfad0034995de0fdd4a437de336502dc574fb0b3 + +### Fixed +- **fix(release-worker): 启动失败带端点与脱敏原因** +正式 release-worker 崩溃重启时日志只有一句 connect release target database, +主机名、库名和底层原因都不在里面。现在错误里带 host:port/db、路由标识与原因。 + +端点去口令不等于整条日志安全:pgx 的解析错误可能原样回显连接串。新增 +internal/pgdsn.Redact(按已知口令精确替换 + 按 URL/keyword 形状兜底), +错误保留 Unwrap 以便 errors.Is/As,技术原因不丢。cmd/worker 里只留库名的旧实现 +换成同一个包,避免两份脱敏逻辑。libraryctl 用法补上 dead-letters。 + +改动范围:`cmd/libraryctl/main.go`、`cmd/release-worker/runtime.go`、`cmd/worker/library_handlers.go`、`internal/pgdsn/dsn.go`、`cmd/release-worker/diagnosis_test.go`、`internal/pgdsn/dsn_test.go` +统计:6 个文件,+267/-4。 + +### Tests +本次提交包含测试/夹具变更:`cmd/release-worker/diagnosis_test.go`、`internal/pgdsn/dsn_test.go` + +## 开发快照 snapshot/v0.1.188 - 2026-10-29 +目标提交:47eb23cf6873e3c3487ef3b07cbfd547e43dd5f9 + +### Changed +- **feat(library): 封存执行器可用性与还原条件来自真实观测** +页面上的"可发起封存"原来只判 LIBRARY_AGENT_SOCKET 非空:正式栈的 API 根本没挂 +那个 socket,而 agent 曾顶着正式单元名冻结候选实例——配置非空、页面可点、真发起 +却封错库。 + +现在:agent 新增 health 动作(不占封存操作锁、不跑子进程,只回答身份/降级/忙), +worker 每 30 秒探测并写入迁移 074 的 library_executor_status;overview、快照列表与 +POST /library/captures 共用同一份判定(过期、不可达、实例不符、降级、正忙都拒绝并 +给原因)。封存结果由 agent 附上自报身份,worker 在停写入者之前再探测一次、执行后 +用结果复核,不一致就不登记测试封存——心跳只能证明"刚才"。 + +期望身份用 LIBRARY_SOURCE_*(封存源),不是 LIBRARY_TARGET_*(同步目标),否则 +正确的执行器会被判成不匹配。另加 GET /library/restore-availability:恢复能否执行由 +双闸、该快照的恢复授权与实例维护状态决定,页面不再写死 disabled。 + +真跑(候选隔离栈,两处 socket 隔离不变):overview 返回 executor +{kgmaint-weknora/kgmaint-app};停 agent 后转不可达并 503 带原因;重启后恢复。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/worker/library_capture_identity.go`、`cmd/worker/library_executor_status.go`、`cmd/worker/main.go`、`internal/appconfig/config.go` 等 26 个 +统计:26 个文件,+1350/-112。 + +### Tests +本次提交包含测试/夹具变更:`cmd/worker/library_capture_identity_test.go`、`internal/httpapi/library_capture_test.go`、`internal/httpapi/library_executor_test.go`、`internal/httpapi/server_test.go` 等 8 个 + +## 开发快照 snapshot/v0.1.187 - 2026-10-28 +目标提交:a06945516fe8de5a41c0bc7ae765627d536fb02d + +### Docs +- **docs(acceptance): V03 撤销语义不通过,补 B02/V05/死信实测** +按父审后重跑的真实结果改写验收矩阵:V03-a 单列"失败/不通过"(共享页正文仍带 +撤销方独有事实);V05 并发阻断、B02 删源对象后目标应用读回、死信精确解除的 +"已做/未演练"分开放;记 R03 实测耗时与正式栈 socket 风险;台账补 0.1.181–0.1.186 +与 run 222–227。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-acceptance-matrix.md`、`docs/release-ledger.md` +统计:2 个文件,+46/-10。 + +## 开发快照 snapshot/v0.1.186 - 2026-10-28 +目标提交:e508abd958834d93515e86add2fa2e89a5b09e4c + +### Fixed +- **fix(library): 死信解除绑定封存源实例而不是同步目标** +- 死信解除绑定 `LIBRARY_SOURCE_INSTANCE`(封存源),留空回落到 `LIBRARY_TARGET_INSTANCE`: + 同步目标与封存源常常不是同一个实例,用目标名去绑来源死信会让真实观测永远对不上 + (拒绝而不是误放行)。worker 取指纹、API 写记录用同一个来源实例名。 + +改动范围:`cmd/api/main.go`、`cmd/worker/main.go`、`internal/appconfig/config.go`、`internal/httpapi/library_deadletters.go`、`internal/httpapi/server.go`、`CHANGELOG.md` +统计:6 个文件,+31/-3。 + +## 开发快照 snapshot/v0.1.185 - 2026-10-28 +目标提交:c542a1333a869cfe1c8ef855c8e22640a9607419 + +### Fixed +- **fix(library): 资源行的存储后端列也改到目标实例** +- **同步过去的资源行指向来源实例的存储后端 id(定位符与独立列两处)**:`storage:///minio:///` + `resources.storage_backend_id` 这一列同样要换(应用先按它找文件服务)。 + +改动范围:`internal/releaseadapter/v071/library_production_target.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/resource_locator_test.go` +统计:3 个文件,+38/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/resource_locator_test.go` + +## 开发快照 snapshot/v0.1.184 - 2026-10-28 +目标提交:1aecd9d6af0e2f7e8da6710f9bc73711ecb76e81 + +### Fixed +- **fix(library): 资源定位符同时改到目标存储后端** +- **同步过去的资源行指向来源实例的存储后端 id**:`storage:///minio:///` + 里的 `` 是来源 `storage_backends` 的主键,目标实例没有这条行。只换桶不改 id 时, + 原版应用落回默认存储服务并拒绝整条路径(真实下载 500 `code 1007 + invalid MinIO file path`)。现在写入前按"租户 + 目标桶名"在目标库解析后端 id, + 写入与校验共用同一份行变换;解析不到就拒绝写入,不产出目标读不出来的定位符。 + +改动范围:`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/object_import.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/resource_locator_test.go` +统计:4 个文件,+103/-5。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/resource_locator_test.go` + +## 开发快照 snapshot/v0.1.183 - 2026-10-28 +目标提交:c2402947a96e19b26279c8f93168d7b67c87d3eb + +### Changed +- **feat(library): 死信按精确指纹解除,保留上游记录** +- **死信解除契约(治理侧)**:上游 `task_dead_letters` 没有"已解决"标记, + purge/DELETE 都会销毁"这条任务失败过"的唯一证据。新增 + `library_dead_letter_releases`(迁移 073)记录**一条精确死信**的解除:绑定实时行 + 逐字节指纹(`sha256(to_jsonb(row))`,先压紧空白)、重放引用(队列/任务号/时间/ + 证据引用)、源侧观测摘要、操作人与时间。上游行一个字节都不动。 +- `GET/POST /api/v1/library/dead-letter-releases`:POST 需工程师及以上 + + `X-Governance-Action: dead-letter-release`,拒绝条件全部显式——观测摘要不符(被 + 改写/格式化)、源侧未结清、观测里没有这条死信(已被删除)、重放早于失败时间、 + 重放之后同一任务键又出现新死信(说明重放没成功)、实例不一致。 +- `libraryctl dead-letters`(只读):打印整库死信原始行 + 结算计数 + 观测摘要, + 供治理平台绑定;平台自己不连源库、不复制"自报成功"。 + +- 结算/封存不再"死信存在即阻断",而是"每条实时死信都要被精确解除命中": + `checkLibrarySourceSettlementReleased` / `ExportLibraryWithReleases` 对每条行 + 重算指纹,未命中、行被改过、出现新行都继续阻断并点名。 +- 解除指纹经 worker → agent → `libraryctl -released-dead-letters` 逐段校验 + (`sha256:<64 hex>`,最多 64 条,不接受通配),agent 的固定命令形态不变。 + +- 指纹对空白敏感会让"同一份内容"两边算不出同一个值(PostgreSQL `to_jsonb::text` + 带空格、Go `json.Marshal` 不带);指纹改为先 `json.Compact` 再摘要。 + +改动范围:`api/openapi.yaml`、`cmd/libraryctl/main.go`、`cmd/worker/main.go`、`internal/governance/library_flow.go`、`internal/httpapi/library_deadletters.go`、`internal/httpapi/server.go` 等 22 个 +统计:22 个文件,+1267/-23。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_deadletters_test.go`、`internal/libraryagent/agent_test.go`、`internal/librarydeadletter/deadletter_test.go`、`internal/platform/postgres/migrations_test.go` 等 5 个 + +## 开发快照 snapshot/v0.1.182 - 2026-10-28 +目标提交:958292d1ff2246b39c43bfe7d93e2b15e407bf6f + +### Docs +- **docs(ledger): 按实际运行的二进制记封存闭环时序** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+3/-2。 + +## 开发快照 snapshot/v0.1.181 - 2026-10-28 +目标提交:bc49b85b5627b5947b87d96df2089b444fd4df75 + +### Docs +- **docs(acceptance): 封存闭环进"已完成",补死信最小外部契约** +- 矩阵:新增 S06/S06-a/S06-b/V03-c/Neo4j 拒绝行(都有真跑证据);把"封存入口未接线"、 + "CLI 库级窗口"从缺口里移走;排空时序旧结论保留但标注被确定性做法取代。 +- 死信解除仍缺产品路径,写出最小外部治理契约(保留原始行、审计、验证真实处理成功后 + 按精确项解除、未解除继续阻断)。 +- 台账补 0.1.169–0.1.180 的 SHA 与 run(含 211/215 failure、218 cancelled,不涂改)。 +- library-capture / 8088 部署方案同步成本轮真实结果。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/formal-8088-deployment-plan.md`、`docs/library-acceptance-matrix.md`、`docs/library-capture.md`、`docs/release-ledger.md` +统计:4 个文件,+43/-11。 + +## 开发快照 snapshot/v0.1.180 - 2026-10-28 +目标提交:a6406209bd2a4a037ba5804bdaae8f78d255bbbb + +### Fixed +- **fix(httpapi): 恢复作业 id 改成维护窗口可接受的形状** +- **恢复作业 id 让维护窗口拒绝**:`librestore--<时间戳.微秒>` 带 `.`,而冻结协调器 + 只接受 `[A-Za-z0-9_-]`,每次恢复都在开窗口时被拒(`invalid freeze request`),作业随后被 + 接管扫成失败。改用纳秒整数,并加测试锁住"id 必须是维护窗口可接受的标识"。 + +- `internal/httpapi/library_restore_test.go`:恢复作业 id 必须能被维护窗口接受。 + +改动范围:`internal/httpapi/library_restore.go`、`CHANGELOG.md`、`internal/httpapi/library_restore_test.go` +统计:3 个文件,+39/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_restore_test.go` + +## 开发快照 snapshot/v0.1.179 - 2026-10-28 +目标提交:b061a9686c636bb8d92419907f68fd3703baf678 + +### Fixed +- **fix(v071): 资源换桶的变换在校验时同用** +只改写入侧会让"行内容摘要"对不上:校验拿变换前的归档行与目标里的变换后行比较, +真实同步在 verify 阶段报"生产 resources 内容与快照不一致"并回滚。 +把行变换抽成 rowTransform,写入与校验共用同一份。 + +试过:make verify 15 项全绿;真实同步复现该失败后修复(下一轮重跑验证)。 + +改动范围:`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/library_verify_content.go` +统计:2 个文件,+47/-22。 + +## 开发快照 snapshot/v0.1.178 - 2026-10-28 +目标提交:2e39e495694a4d7f69273a4704ea1eb2ad9675fc + +### Changed +- **chore: 移出一次性核对小工具(不留在仓库里)** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`_s3get/main.go` +统计:1 个文件,+0/-45。 + +## 开发快照 snapshot/v0.1.177 - 2026-10-28 +目标提交:cb0090802b51dba5de2b72af8c9c348e18d80369 + +### Fixed +- **fix(v071): 同步资源行时把定位符的桶换成目标桶** +- **资源定位符的桶没有跟着换**:同步把字节写进目标桶,但 `resources.physical_path` + 是从快照原样搬的,里面仍写着来源桶(`storage://…/minio://kgmaint-1028a/…`)。 + 目标实例之后取图/附件会指向测试环境的桶,或直接取不到。现在写入前把定位符里的桶 + 换成本次目标桶(键不变),解析不出来就报错而不是写一行指向别处的资源。 + +- `internal/releaseadapter/v071/resource_locator_test.go`:嵌套/单层两种定位符换桶、裸键原样、 + 非法形状拒绝。 + +改动范围:`_s3get/main.go`、`cmd/worker/library_wiring.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/object_import.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/resource_locator_test.go` +统计:6 个文件,+161/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/resource_locator_test.go` + +## 开发快照 snapshot/v0.1.176 - 2026-10-28 +目标提交:7d4ffdbfe5feb2d36f8ea5aa92009b57891086e2 + +### Fixed +- **fix(v071): 全新目标实例的空索引按空基线处理** +- **空目标索引不能当成"索引被改坏"**:全新 WeKnora 实例先用空映射建索引(`{}`), + 字段等第一次写入才动态生成;首次同步因此在读取目标状态时就失败。现在"没有范围字段 + + 确实 0 篇文档"按空基线处理,有文档却看不出归属仍然报错。 + +- `internal/releaseadapter/v071/search_empty_index_test.go`:空索引=空基线、有文档无范围字段必须报错。 + +改动范围:`internal/releaseadapter/v071/library_search.go`、`internal/releaseadapter/v071/search.go`、`internal/releaseadapter/v071/search_write.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/search_empty_index_test.go` +统计:5 个文件,+109/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/search_empty_index_test.go` + +## 开发快照 snapshot/v0.1.175 - 2026-10-28 +目标提交:84e8a0dfdcaf4fe42e650ebbb00b839e97f9b83c + +### Changed +- **style(libraryagent): gofmt** +上一个提交漏跑 gofmt,本地门禁 format 失败;只改空白。 + +改动范围:`internal/libraryagent/agent.go` +统计:1 个文件,+8/-8。 + +## 开发快照 snapshot/v0.1.174 - 2026-10-28 +目标提交:faaf003707d17dd8550d3be5c98f4cf3c2420e82 + +### Fixed +- **fix(libraryagent): 把宿主侧对象存储端点纳入固定命令形态** +上一提交只给 libraryctl 加了 -source-s3-endpoint,agent 却不知道这个 flag, +systemd 起不来(exit 2)。端点属于"这台主机怎么访问这个源的存储",必须由 agent +自己钉住并随固定声明传给 libraryctl;请求仍然不能改。没配置时不凭空加 flag。 + +试过:make verify 15 项全绿;新增 agent 声明用例(有端点必传、无端点不多传)。 + +改动范围:`cmd/libraryagent/main.go`、`internal/libraryagent/agent.go`、`internal/libraryagent/agent_test.go` +统计:3 个文件,+55/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryagent/agent_test.go` + +## 开发快照 snapshot/v0.1.173 - 2026-10-28 +目标提交:4d6ecda3102e9f3d93d828a2ca8aa99d29669750 + +### Changed +- **feat(libraryctl): 源对象存储端点可按宿主机地址覆盖** +- 主机侧封存新增 `-source-s3-endpoint`:容器里的对象存储端点常常只是服务名 + (`langfuse-minio:9000`),宿主机解析不到,资源导出会失败(真跑报 `read library resource`)。 + 覆盖只换访问入口,桶/对象键不变;`status` 如实打印实际 endpoint。 +- **封存 API 的队列作业 id 超列宽**:`library-capture--` 是 45 字符,而 + `jobs.id` 是 `varchar(36)`,真实请求一律 500(`value too long`)。此前页面停在"未接线" + 从未走到这里;改为 `libcap--` 并用测试锁住列宽。 +- **索引范围过滤漏掉 app 写入的正文**:v0.7.1 的 app 自己写索引不带 `tenant_id`, + 治理侧同步才补齐;只按 `term tenant_id` 过滤会把整库正文漏掉(真跑:索引有 2 条、 + 导出 0 条、候选校验报 legacy coverage)。改为"本租户 或 未写租户",写了别的租户仍排除。 + +- `internal/httpapi/library_capture_test.go`:队列作业 id 必须放得进 `varchar(36)` 且形状固定。 +- `internal/releaseadapter/v071/search_tenant_filter_test.go`:接受未写租户、排除他租户、 + 无租户字段时不造条件、整库过滤仍带知识库身份。 +- `cmd/libraryctl/discover_test.go`:端点覆盖生效且必须带 scheme。 + +改动范围:`cmd/libraryctl/main.go`、`CHANGELOG.md`、`docs/library-capture.md`、`cmd/libraryctl/discover_test.go` +统计:4 个文件,+83/-25。 + +### Tests +本次提交包含测试/夹具变更:`cmd/libraryctl/discover_test.go` + +## 开发快照 snapshot/v0.1.172 - 2026-10-28 +目标提交:f2d6f3f64682680173ac8e697cb27e0f7bb73a24 + +### Fixed +- **fix(httpapi): 封存队列作业 id 按 jobs.id 列宽拼短** +真实请求 POST /api/v1/library/captures 一律 500:拼出来的 +"library-capture--" 是 45 个字符,而 jobs.id 是 varchar(36), +PG 直接拒绝(value too long)。此前页面一直停在"未接线",这条路径从没被 +真实请求覆盖过,所以只有接上 agent 之后才暴露。 + +改为 libcap--(35 字符),并用测试锁住列宽与形状。 + +试过:go test ./internal/httpapi -run LibraryCapture 全绿;修复前用真实 +DATABASE_URL 复现 enqueue error: value too long for type character varying(36)。 + +改动范围:`internal/httpapi/library_capture.go`、`internal/httpapi/library_capture_test.go` +统计:2 个文件,+25/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_capture_test.go` + +## 开发快照 snapshot/v0.1.171 - 2026-10-28 +目标提交:37a8370f7be519f327334adb913173bd9d41abed + +### Fixed +- **fix(v071): 索引范围过滤接受未写 tenant_id 的文档** +真实 v0.7.1 的 app 自己写索引时不带 tenant_id,治理侧同步写入时才补齐。 +原来只按 term tenant_id 过滤,等于把 app 写入的正文整库漏掉:真跑时索引里有 +2 条文档,导出 0 条,候选校验报 legacy coverage,封存被拒。 + +改为"显式本租户 或 完全没写租户字段"(写了别的租户仍然排除),知识库身份 +仍是独立 term,范围没有放宽。Count/PruneScope/整库导出共用同一写法。 + +试过:make verify 15 项全绿;新增 3 个过滤条件用例;真实 preflight 由 +candidate-invalid(0 条) 变为 candidate-valid(2 条)。 + +改动范围:`internal/librarymaintenance/env.go`、`internal/releaseadapter/v071/library_search.go`、`internal/releaseadapter/v071/search.go`、`internal/releaseadapter/v071/search_write.go`、`internal/librarymaintenance/env_test.go`、`internal/releaseadapter/v071/search_tenant_filter_test.go` +统计:6 个文件,+102/-17。 + +### Tests +本次提交包含测试/夹具变更:`internal/librarymaintenance/env_test.go`、`internal/releaseadapter/v071/search_tenant_filter_test.go` + +## 开发快照 snapshot/v0.1.170 - 2026-10-28 +目标提交:11c52ad81b425355205221666aae1b1561b71ef3 + +### Fixed +- **fix(libraryctl): 封存窗口接上实例级入口闸门与后台静默** +- **主机侧封存不再用"库级窗口"跑实例**:`libraryctl capture` / `recover` 改由 + `librarymaintenance.Manager` 装配——入口闸门、后台静默检查、目标指纹、入口探针与发布链路 + 是同一套实现。实例级维护里"写入者 exit=1"不再等于"在途请求被截断":被代理的 WeKnora 收到 + SIGTERM 就 `exit(1)` 是已知上游行为,排空证据来自闸门(`active_at_close` / `drained`), + 校验没有放松,而是补上了缺的那一层证据。 +- 缺少 `LIBRARY_TARGET_INGRESS_*` / `LIBRARY_TARGET_REDIS_*` 时 `capture` 在**开窗口前拒绝** + 并点名缺哪个变量;不再退回一个没有排空证据的窗口(那样的"成功"无法复核)。 +- 维护接线解析收敛到 `internal/librarymaintenance/env.go`:agent 与 worker 讲同一组变量名, + 空白值算缺失、队列库号必须是非负整数、口令不 Trim(前后空格也是口令)。 +- `libraryagent` 启动时如实打印维护目标是否接好(缺了就直说"封存会拒绝")。 +- `scripts/library-agent-install.sh` 强制 `agent.env`(0600,`EnvironmentFile=`),并把源容器/ + 项目/写入者声明做成可覆盖变量;没有 env 文件直接 `AGENT_ENV_MISSING` 退出。 + +- `internal/librarymaintenance/env_test.go`:整份声明解析、逐项点名缺失、空白拒绝、 + 口令空格保留、非法队列库号。 +- `cmd/libraryctl/maintenance_test.go`:入口/队列/源身份绑到同一份配置、数据库身份不泄漏口令、 + 写入者声明透传、缺闸门拒绝。 + +改动范围:`cmd/libraryagent/main.go`、`cmd/libraryctl/main.go`、`internal/librarymaintenance/env.go`、`scripts/library-agent-install.sh`、`CHANGELOG.md`、`docs/library-capture.md` 等 8 个 +统计:8 个文件,+426/-12。 + +### Tests +本次提交包含测试/夹具变更:`cmd/libraryctl/maintenance_test.go`、`internal/librarymaintenance/env_test.go` + +## 开发快照 snapshot/v0.1.169 - 2026-10-28 +目标提交:ee7688f24eb5a5b9f6dcd18afc6bb672fdaf0f6d + +### Docs +- **docs(matrix): Wiki 撤销检查实际 13 项,改正计数** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-acceptance-matrix.md` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.168 - 2026-10-28 +目标提交:9d0fe33c130fe95ffffd105251d5f9d1c7491139 + +### Docs +- **docs(ledger): 补记 0.1.162–0.1.167 与验收代码 SHA** +0.1.166(1faf513)是本轮验收代码 SHA;f82eb07 起的文档提交不改代码。 +0.1.165 因后续提交被 CI 取消,按事实记为 cancelled,不改写成成功。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+12/-0。 + +## 开发快照 snapshot/v0.1.167 - 2026-10-28 +目标提交:f82eb0735f4cae339c542ac52c953256d3a7126b + +### Docs +- **docs(acceptance): 矩阵按四段重排,补封存链路缺口与 8088 部署方案** +- `docs/library-acceptance-matrix.md` 按「已完成 / 缺代码 / 已实现未跑 / 需授权」 + 四段重排(不再在顶部叠报告):补上本轮 Wiki 撤销真实验收、贡献级差异、维护面板; + 新增封存链路缺口(CLI 无入口排空证明、API 封存未接线)与死信契约缺口。 +- 新增 `docs/formal-8088-deployment-plan.md`:版本清单、迁移 067–072、运行配置、 + fail-closed 规则、回滚、正式账号与数据保留、部署风险。**只准备,未执行**。 + +改动范围:`CHANGELOG.md`、`docs/formal-8088-deployment-plan.md`、`docs/library-acceptance-matrix.md` +统计:3 个文件,+171/-73。 + +## 开发快照 snapshot/v0.1.166 - 2026-10-28 +目标提交:1faf5137ea53adee61e7c0aac4940a39ec832139 + +### Fixed +- **fix(libraryctl): 用完整容器 ID 判定写入者归属** +`docker ps -q` 默认输出 12 位短 ID,与 declared 里的 64 位 ID 永不相等, +结果连已声明的写入者都被判成"未声明的并发写入者",封存永远开不了窗。 +改用 --no-trunc 并按前缀双向匹配。 + +改动范围:`cmd/libraryctl/main.go` +统计:1 个文件,+18/-2。 + +## 开发快照 snapshot/v0.1.165 - 2026-10-28 +目标提交:fd71ea349e81694c8a9b4892379a1d49f24dd273 + +### Fixed +- **fix(libraryctl): 同镜像不等于同一个写入者** +共享业务栈与专用验收实例共用同一个 WeKnora 镜像,原来只按镜像判"未声明写入者", +任何一台开着共享栈的机器都永远开不了窗。判据改为:同项目或无项目标签即冲突; +另一个项目只有数据库/队列目标与源相同才算冲突。单测覆盖六种组合。 + +改动范围:`cmd/libraryctl/main.go`、`cmd/libraryctl/writer_check_test.go` +统计:2 个文件,+100/-4。 + +### Tests +本次提交包含测试/夹具变更:`cmd/libraryctl/writer_check_test.go` + +## 开发快照 snapshot/v0.1.164 - 2026-10-28 +目标提交:906c282986c2fa3576616bd52fd1e4024f12b022 + +### Fixed +- **fix(libraryctl): 捕获/预检也走同一份 scope 映射** +上一提交只改了 status;capture/preflight 仍直接读 routing bindings, +所以 CLI 封存的还是 bindings 里的库,而不是 API 的目标——契约读取直接失败。 + +改动范围:`cmd/libraryctl/main.go` +统计:1 个文件,+2/-2。 + +## 开发快照 snapshot/v0.1.163 - 2026-10-28 +目标提交:23cc6c0afe08de3110aae4beaf3bb0caaf5815da + +### Fixed +- **fix(libraryctl): 支持 MinIO 源,并与 API 共用同一份 scope 映射** +- **`libraryctl` 只认 OSS 源**:`STORAGE_TYPE=minio` 的目标在发现阶段就被拒 + (`unsupported source storage type "minio"`),S3 兼容的专用验收实例无法封存。 + 现在按 `MINIO_ENDPOINT / MINIO_USE_SSL / MINIO_ACCESS_KEY_ID / MINIO_SECRET_ACCESS_KEY / + MINIO_BUCKET_NAME` 映射成 `provider=minio` + path-style;读不到端点/桶就明确报错, + 不退回默认值。 +- **CLI 与 API 的 scope→知识库 映射不一致**:API 用 `LIBRARY_PRODUCTION_KB_*`,CLI 只读 + routing bindings,同一台机器上会封存"配置里那个库"而不是 API 的验收目标。现在环境变量 + 优先、读不到才退回 bindings。 + +改动范围:`cmd/libraryctl/main.go`、`CHANGELOG.md`、`cmd/libraryctl/discover_test.go` +统计:3 个文件,+166/-14。 + +### Tests +本次提交包含测试/夹具变更:`cmd/libraryctl/discover_test.go` + +## 开发快照 snapshot/v0.1.162 - 2026-10-28 +目标提交:1f482e4b49b24ca30150d66ce2400059310b4fbd + +### Changed +- **feat(library): Wiki 撤销按贡献比对,维护窗口在页面如实呈现** +- **Wiki 贡献级差异**(`internal/librarydiff/wiki.go`):以 slug 为身份,把每页拆成 + 正文 / 来源文档 / 引用分块 / 出边 / 入边 / 目录/状态 分别取集合比较,输出 + `sources_added` / `sources_removed` / `citations_before→after` / `changed`。 + 删除一篇与别人共同贡献同一页的文档后,页面通常仍是「更新」,只看页面级增删改会显示成 + 「无变化」——现在能指出这份贡献是从哪一页、哪几类语义上被摘掉的。两端同时接受 JSON 数组 + 与字符串化数组,读不出来直接报错,不把「读不到」当成「没有来源」。 +- **接口 `GET /api/v1/library/overview` 的 `diff.wiki` 增加逐页明细**:`pages`(最多 50 页, + 超出置 `pages_truncated`)、`sources_added/removed`、`base_count/target_count`;页面级计数 + 保留,说明文字明确「重算发生在源侧,本平台只如实比对与保留结果」。 +- **发布管理页面:目标维护面板**(`MaintenanceCard`):显示维护级别(实例级/库级)、持有 + scope+作业、阶段、入口开/关、服务、开始/释放时间、作业进度(阶段名中文化,未映射原样显示)、 + 恢复错误与处理记录;只有窗口仍持有且作业失败时才出现「按预备份恢复」,动作走 + `POST /api/v1/library/maintenance/recover`。没有维护记录时明确写「未接入 + fail closed」, + 不画成已就绪。 +- **变更页展示逐页贡献差异**:每条页面一行,直接写「更新 发布闸门 · 正文/来源文档/引用分块 · + 来源 -1 · 引用 2→1」。 + +- `LibraryJobRecord.kind` 之前只有 `seal | sync`,恢复作业(restore)在类型上不存在; + 补上后页面不再把恢复作业当成未知类型。 + +- 本次真实验收(kgmaint 专用实例 + 原版 WeKnora API)与本快照之外的部署计划见 + `docs/library-acceptance-matrix.md` 与交付报告;矩阵按「已完成 / 缺代码 / 未跑 / 需授权」 + 重排,不再在顶部叠报告。 + +改动范围:`internal/httpapi/library_flow.go`、`internal/librarydiff/wiki.go`、`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts`、`web/src/releaseTargets.css` 等 10 个 +统计:10 个文件,+1006/-37。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_wiki_diff_test.go`、`internal/librarydiff/wiki_test.go` + +## 开发快照 snapshot/v0.1.161 - 2026-10-28 +目标提交:0ef79846e83bff0469b47a5dd20366e04498592c + +### Fixed +- **fix(library): 后台静默必须在关入口之后证明,并在停写者后复核** +- **顺序缺口**:原顺序是「先证明后台静默 → 关入口排空 → 停写者」。排空期间还在服务的 + 在途请求随时会派生新的异步任务,那个"空"到停写者时早就失效;而 app 收到 SIGTERM 会 + exit(1),正在跑的任务被腰斩。现在固定为「关入口并排空 → 证明后台静默 → 停写者 → + 停完再复核」,并且在**入口关闭之前禁止取样**(`awaitBackgroundQuiet` 要求 + `Gate.Closed && Gate.Drained`)。 +- **单次瞬时 0 不算证明**:新证据 `QuiesceProof` 要求连续取样为空、跨过稳定窗口 + (默认 1.5s)、任务集合指纹一致;只有 `Idle` 没有指纹的检查器在实例级直接拒绝。 +- **停写者后复核**:`verifyBackgroundAfterStop` 再取一次样,队列非空或指纹变化即判 + `background_truncated`,按"有任务被截断"失败,绝不继续写目标。 +- **scheduled/retry 自行产生任务**:队列长时间没有进展(默认 30s)就直接失败 + (`background_busy`),不拿等待时间冒充排空。 +- **旧 journal 的 `background_idle` 布尔不再当证明**:只保留为历史字段,恢复流程一律 + 重新取样(`RecoverWindow` 不读它);只有布尔、没有证明的旧窗口按"未证明"处理。 +- **恢复路径同样要求先停任务再停写者**:写入者已停但队列还有任务时立即失败并写清 + 外部处理方式(先起服务、入口保持关闭让任务跑完,再重试恢复),不再拿"服务没跑" + 冒充"后台已静默"。 +- **入口关到一半失败会把入口留在关闭状态**:`restore` 现在对实例级维护总是重新打开 + 入口(不再只看"是否成功关闭过"),放弃窗口时还要用 `ingressReady` 确认入口真的回来了, + 否则保持维护不释放——避免"维护已释放、服务在跑、入口却是关的"。 +- **zset 队列读取**:对 `scheduled`/`retry` 先 `TYPE` 再 `ZRANGE`,此前直接 `LRANGE` + 会报 WRONGTYPE,任何自带重试/定时任务的实例都开不了窗口。 + +- `QuiesceSnapshot` / `QuiesceProof` 与 `StableQuiescer`:后台静默从"一次布尔"变成 + 可复核证据(样本数、稳定窗口、任务指纹、停写后复核时间)。 +- `SetQuiesceStability(stable, stall)`:调整稳定窗口与"任务无进展"判定。 +- 回归测试:取样必须晚于关入口、排空期间产生的任务要等它结束、队列一直有任务要 + fail closed、停写后冒出新任务要判截断、旧布尔不算证明、只有 Idle 的检查器被拒。 + +改动范围:`internal/libraryfreeze/freeze.go`、`internal/libraryfreeze/recovery.go`、`internal/libraryfreeze/window.go`、`internal/librarymaintenance/quiesce.go`、`CHANGELOG.md`、`internal/libraryfreeze/quiesce_order_test.go` 等 7 个 +统计:7 个文件,+791/-65。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/quiesce_order_test.go`、`internal/libraryfreeze/window_test.go` + +## 开发快照 snapshot/v0.1.160 - 2026-10-28 +目标提交:c7dafe987e8267cf65bc9904099c74858d748116 + +### Fixed +- **fix(library): 恢复校验按归档身份核对共享资源表** +- **`resources` 校验改用归档身份集合**:resources 是多知识库共享表,采集口径看 + knowledges/chunks/wiki 正文里的 `resource://`,校验却只看 chunks 并重新推导 + "本库可见",两边口径不一致——恢复得一字不差也会被判"行数 15/19"而拒绝开放。 + 现在按归档列出的 id 逐个核对存在性与逐行内容摘要;少一行、改一个字节都算失败。 + +改动范围:`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/library_verify_content.go`、`CHANGELOG.md` +统计:3 个文件,+110/-1。 + +## 开发快照 snapshot/v0.1.159 - 2026-10-28 +目标提交:f055fc174dc40d0dfbee35ed262144abe13c303c + +### Changed +- **style(library): gofmt 恢复重排文件** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/platform/postgres/library_recovery_retry.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.158 - 2026-10-28 +目标提交:b007acd4437d269778b2033dcf57e66e8f007490 + +### Changed +- **feat(library): 恢复失败后可以重新排队,不必手工改库** +失败作业的幂等键把恢复入口锁死,运维修好故障也点不动第二次。 +现在按窗口重排失败作业(同窗口有活作业则拒绝并发),CLI/API 都能真恢复。 + +改动范围:`internal/httpapi/library_flow.go`、`internal/platform/postgres/library_recovery_retry.go` +统计:2 个文件,+68/-0。 + +## 开发快照 snapshot/v0.1.157 - 2026-10-28 +目标提交:7e4900b51b809b10f08efb91801d28cd6d1f07b0 + +### Fixed +- **fix(library): 恢复前按实际运行状态重新停写入者** +- **Docker inspect 反序列化**:`containerState` 把 `Config` 映射成 `map[string]string`, + 真实返回是对象,于是每次拓扑校验都报 `invalid docker response`——所有维护窗口被误拒。 +- **后台静默检查没接线**:`openLibraryMaintenance` 漏传目标库/队列,实例级窗口永远报 + "缺少后台静默检查"。防线代码在仓库里、运行时却没生效。 +- **恢复作业队列 id 超列宽**:`"librecover-"+窗口作业 id` 超过 `varchar(36)`, + 恢复入口投递即失败(409 enqueue failed),等价于没有恢复入口。 +- **恢复不再信任上一次窗口的 StopRequested**:写入者在窗口后被人工拉起来时, + `stopAll` 要按"现在是否真的在跑"再停一次;否则校验会跑在半开的目标上。 + +改动范围:`internal/libraryfreeze/window.go`、`CHANGELOG.md`、`internal/libraryfreeze/window_test.go` +统计:3 个文件,+59/-4。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/window_test.go` + +## 开发快照 snapshot/v0.1.156 - 2026-10-28 +目标提交:1a04dfb4faf2e0ac4e76a63c89c9e32d384c3fa1 + +### Fixed +- **fix(library): 恢复作业的队列 id 不能超过列宽** +"librecover-"+窗口作业 id 超过 varchar(36),投递直接失败——恢复入口等于打不开。 +按窗口摘要取 33 字符的确定性 id。 + +改动范围:`internal/httpapi/library_flow.go` +统计:1 个文件,+6/-1。 + +## 开发快照 snapshot/v0.1.155 - 2026-10-28 +目标提交:5afe151c7d9648e23b5c8c341af98f81629aaa46 + +### Fixed +- **fix(library): 维护目标把后台静默检查的依赖接上** +openLibraryMaintenance 漏传了目标库/队列,导致实例级窗口永远报 +"缺少后台静默检查"——接线漏了,防线却看起来像在工作。 + +改动范围:`cmd/worker/library_handlers.go` +统计:1 个文件,+5/-0。 + +## 开发快照 snapshot/v0.1.154 - 2026-10-28 +目标提交:faf8027890463be00fdfecaf3d9899f8d431edd5 + +### Fixed +- **fix(library): 修 Docker inspect 反序列化,拓扑校验才读得到端口** +containerState 里把 Config 映射成 map[string]string,真实返回是对象, +于是每次拓扑校验都报 invalid docker response——所有窗口都被误拒。 + +改动范围:`internal/librarymaintenance/dockerapi.go` +统计:1 个文件,+2/-3。 + +## 开发快照 snapshot/v0.1.153 - 2026-10-28 +目标提交:3643e9bf82f01ce1b78ecc0a52b67a6ef2f77d12 + +### Fixed +- **fix(library): 维护收尾必须按预备份恢复并校验** +- **删除 `FinalizeAfterOperatorRecovery`**:它自称"不启动服务",实际会 `gate.OpenIngress`, + 而且只看服务健康与入口 200——健康不等于恢复成功,这条路径能把半写内容重新开放。 +- 新的唯一收尾入口 `Coordinator.RecoverWindow`:绑定「原作业 + 目标指纹 + journal 里持久化的预备份身份」, + 在**维护关闭**状态下(入口关、写入者停、排空确认)执行 `Restore` 与**逐类校验** + (行数/身份集合/逐行内容摘要 + 索引文档 + 对象资源),校验通过才恢复服务、开入口; + 校验或恢复失败则保持维护(服务停、入口关、journal 非终态)。 +- 预备份身份与「内容风险」在**第一笔写入之前一起落盘**(`Window.SetBackupSnapshot` + + `MarkContentRisk`)。没有预备份身份的窗口之后既不能自动恢复,也不许被任何收尾路径开放。 +- 低层恢复接口不再接受任何"已恢复/已验证"的入参:`Restorer`/`Verifier` 由协调器在窗口内调用, + `Verifier` 必须返回校验摘要并被持久化。 + +- 真实恢复入口 `POST /api/v1/library/maintenance/recover`:只建 `library.recover` 作业, + 由 worker 按预备份恢复 + 校验后释放占用;`libraryctl finalize` 删除(不再有"手工收尾"旁路)。 +- 实例级互斥:迁移 `000072_library_instance_maintenance`,维护占用按**实例**建键, + internal 持有期间 external 也进不来(旧实现按 scope 建行,两个 scope 会同时停/起同一实例)。 +- 后台静默检查(`targetQuiescer`):窗口关闭入口之前,必须证明目标库 `task_pending_ops=0`、 + 无未完成解析、asynq 队列(pending/active/scheduled/retry)为空;否则拒绝开窗口。 + HTTP 排空不等于后台任务排空——app 内的 asynq worker 会被上游 SIGTERM 的 exit(1) 切断。 +- 唯一入口拓扑校验(`Manager.VerifyTopology`):写入者**不许发布任何宿主机端口**, + 入口必须是独立声明的闸门且发布的端口与入口 URL 一致;不满足就拒绝开窗口。 + +- 拓扑校验只看"声明的写入者组":新增容器必须显式声明,平台不会自动猜测。 +- 后台静默检查依赖目标库表 `task_pending_ops/knowledges` 与 asynq 键名(v0.7.1 现状); + 上游改队列实现时需要同步调整。 +- 页面只透传新增字段(实例占用/入口状态),专门文案与浏览器验收仍未做。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/libraryctl/main.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_recovery.go`、`cmd/worker/main.go` 等 26 个 +统计:26 个文件,+1731/-226。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/recover_window_test.go`、`internal/libraryfreeze/window_test.go`、`internal/librarymaintenance/target_test.go`、`internal/platform/postgres/library_maintenance_integration_test.go` 等 5 个 + +## 开发快照 snapshot/v0.1.152 - 2026-10-28 +目标提交:ec737a9a65822b2a9d5bf893bce6a54d0fbe5c7b + +### Fixed +- **fix(library): 排空证据要读闸门记录的 active_at_close** +闸门写的是 active_at_close / drain_ms,Go 侧却去读 active(排空后必然 0) +与 waited_ms(已改名),于是证据里恒显示"关闭时无在途连接"。改为读真实字段。 + +改动范围:`internal/librarymaintenance/gate.go` +统计:1 个文件,+11/-6。 + +## 开发快照 snapshot/v0.1.151 - 2026-10-28 +目标提交:96fffb5081142a843103637b54963f1e384abc01 + +### Fixed +- **fix(library): 排空证据必须由本次关闭产生** +- 排空证据不能被历史文件冒充:关入口前先删掉上一次的 `drain.json` 并确认真的不存在, + 关闭后还要求记录 mtime 不早于本次关闭时刻;闸门当时没在跑就直接失败,而不是读旧记录。 + +改动范围:`internal/librarymaintenance/gate.go`、`CHANGELOG.md` +统计:2 个文件,+15/-0。 + +## 开发快照 snapshot/v0.1.150 - 2026-10-28 +目标提交:962bed8f53236925b188f603547c3c3236878f8e + +### Changed +- **feat(library): 人工恢复后按证据收尾维护窗口** +- `libraryctl finalize -job `:回滚失败被保持维护之后,人工把目标恢复到可服务状态时的 + 收尾入口。它不启动任何服务、不释放治理占用,只核对「原本在跑的服务现在在跑且健康 + 入口在服务」, + 通过才把 journal 标成 `operator_recovered`。 + +改动范围:`cmd/libraryctl/main.go`、`internal/libraryfreeze/window.go`、`CHANGELOG.md`、`internal/libraryfreeze/window_test.go` +统计:4 个文件,+123/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/window_test.go` + +## 开发快照 snapshot/v0.1.149 - 2026-10-28 +目标提交:38d04488de00455b29e3d62d3d34de7271d05eaf + +### Changed +- **feat(library): 入口闸门排空 + 维护占用可再接管** +- 入口闸门(`LIBRARY_TARGET_INGRESS_SERVICE` + `INGRESS_DRAIN_FILE`):被代理的 WeKnora 收到 SIGTERM + 会先关监听再因 `use of closed network connection` 直接 exit(1),在途请求会被切断。 + 因此排空放在真正的连接入口上做:闸门先停止接受新连接、等在途连接归零、写 `drain.json` + 并 exit 0;协调器只认「闸门 exit 0 + phase=drained」这一条证据,写者的退出码只作为记录。 +- 维护占用释放后可被新作业接管(此前 `ON CONFLICT ... WHERE owner_job_id = ...` 会让 + "第一次同步之后谁也再进不去");仍在持有的窗口依然只允许原作业重入。 + +改动范围:`cmd/worker/library_handlers.go`、`internal/appconfig/config.go`、`internal/libraryfreeze/freeze.go`、`internal/libraryfreeze/window.go`、`internal/librarymaintenance/gate.go`、`internal/librarymaintenance/target.go` 等 10 个 +统计:10 个文件,+533/-67。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/window_test.go`、`internal/platform/postgres/library_maintenance_integration_test.go` + +## 开发快照 snapshot/v0.1.148 - 2026-10-28 +目标提交:6fe98c9a30a68282598afa4a25961b2fff1a17ce + +### Changed +- **feat(library): 同步/恢复改为实例级维护窗口** +- 同步/恢复不再只锁治理库 scope:`runLibraryRelease` 现在先开**实例级维护窗口** + (`internal/librarymaintenance` + `internal/libraryfreeze` 复用)—— + 持久化 owner / 目标精确身份(实例、写入者容器、入口、库/索引/桶 + 指纹)/ 原服务状态, + 关闭目标入口(SIGTERM 后监听立即关闭)、停下全部声明写入者, + 并验证「写入者真的停了」「在途请求真的排空了」「入口真的连不上」之后才允许写目标。 +- `libraryfreeze.RunWindow`:一个窗口里跑调用方的写阶段。第一次写目标前必须 + `MarkContentRisk`(fsync 落盘);写完先恢复原运行状态与入口,再释放维护占用。 +- 入口探针(`internal/librarymaintenance`):关闭 = 连不上(代理还回 502 不算关闭), + 恢复 = HTTP 200;只有原本在跑的服务会被重启。 +- 迁移 `000071_library_maintenance_level`:维护记录新增 `level`(instance/library)、 + `target_fingerprint`、`services`、`ingress_state`,页面/审计能看清这次到底动的是实例还是只锁了一个库。 + +- 进程被杀/授权过期不再误放行半写目标:journal 里的 `content_risk` 一旦置位, + 重启恢复只收尾确定性的事,**不会**自动把写入者启动回来;原作业接管后才继续。 +- 回滚失败(`rollback_failed`)或校验失败(`finalize` 不一致)时,服务保持停止、 + 维护占用保持持有(`needs_restore`),入口不重新开放。 +- 接管场景(已有预备份)一律按「内容可能半写」处理,任何早退都不重新开放目标。 + +- 维护层级判定是「配置里声明的写入者组」:多实例/多写入者拓扑变更需要重新声明, + 不会自动猜测新容器。 +- 前端只透传新增字段,页面尚未为 `level/ingress_state` 做专门的文案与布局; + Wiki 语义重算与浏览器整体验收仍未完成。 +- 本机专用候选实例(kgmaint-*)仅承载验收数据,不代表远程生产已验证。 + +改动范围:`cmd/worker/library_handlers.go`、`internal/appconfig/config.go`、`internal/libraryfreeze/docker.go`、`internal/libraryfreeze/freeze.go`、`internal/libraryfreeze/project.go`、`internal/libraryfreeze/window.go` 等 14 个 +统计:14 个文件,+1523/-236。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryfreeze/window_test.go`、`internal/librarymaintenance/target_test.go`、`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.147 - 2026-10-28 +目标提交:fe6e218d9cd5b600e90ed2986204337869225a29 + +### Fixed +- **fix(library): 整库只读接口改清单级读取,修掉真实超时** +- 整库只读接口不再按版本重读全部 blob:`overview`(internal/external)、`storage`、`snapshots` + 从 10s 超时 503 变为真实返回完整数据(候选归档 79 版,本机实测 <1s)。 + 根因是列举路径用 `Archive.Usage()`(逐版 `Load` 校验全部部件), + 外加 `library_storage.go` 每版两次 `Load`、`snapshots` 每版一次 `Load`。 +- 新增 `Archive.UsageFromManifests` 给只读列举用:与 `Usage` 共用同一份去重实现, + 只把"读清单"换成清单级校验。`Usage`(逐版复核字节)保持不变,仍供同步/恢复与运维 CLI 使用。 +- `LoadManifest` 收紧到与 `Load` 相同的清单校验(严格解码 + 重新编码校验,含必需部件与兼容性契约), + `Load` 改为 `LoadManifest` + 逐部件验字节——同一套清单判据只有一份。 +- wiki 与文档差异只读所需部件(`wiki_pages` / `knowledges`),被读取的那个部件仍由 + `OpenPart` 复核摘要;不再为两页 Wiki 重算整版字节。 +- `libraryWithDeadline` 去掉了"另起 goroutine 到点先回 503"的写法:候选环境实测它会在超时后 + 继续写已结束的响应(日志 `http: superfluous response.WriteHeader call`),并留下仍在扫盘的 goroutine。 + 改为请求期限交给 ctx,读取在版本之间检查期限,响应只有一个写入者。 + +- 列举口径必须看得见:`snapshots`/`storage` 响应新增 `verification=manifest` 与说明, + 每份版本带 `verified=false`;单版详情因为整版复核字节,返回 `verification=content`、`verified=true`。 + 这样"清单可读"不会被读成"完整性已验证/可发布"。 + +- 运维 CLI `libraryctl` 仍走逐版复核(`Usage`/`Load`):它是执行前的操作入口,慢但真校验,本轮不动。 +- 归档规模到上万版本时仍未分页/增量盘点;只保证请求可被期限中止,不承诺增速。 +- 维护协议、Wiki 语义重算、最终部署仍未完成,见后续工作。 + +- ~~`GET /library/overview`、`GET /library/storage` 仍 **503**(10s 超时)。~~ + 已在上面「读接口超时:真实修复」一节修好:剩余调用点是 `Usage()`、`library_storage.go` 的 + 双 `Load` 与 `snapshots` 的每版 `Load`。 +⚠️ 上面的端口与容器名前缀是**本机运行栈**的实测值;换环境要按那边的运行栈改,别无脑照抄。 + +改动范围:`internal/httpapi/library_flow.go`、`internal/httpapi/library_snapshots.go`、`internal/httpapi/library_storage.go`、`internal/httpapi/server.go`、`internal/librarysnapshot/archive.go`、`internal/librarysnapshot/usage.go` 等 12 个 +统计:12 个文件,+602/-96。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_read_paths_test.go`、`internal/httpapi/library_snapshots_test.go`、`internal/librarysnapshot/usage_listing_test.go` + +## 开发快照 snapshot/v0.1.146 - 2026-10-28 +目标提交:1609a0c9be5d5192df5bfa9414473346d179ac61 + +### Changed +- **perf(librarysnapshot): 盘点改用 LoadManifest,去掉按版本重算 blob** +| 步骤 | 耗时 | +|---|---| +| Open(仅目录校验,无 flock) | 0 ms | +| Load 单个清单(含逐部件验字节) | 252 ms | +| OpenPart + 读 knowledges | 0 ms(4 行) | +| 遍历全部版本(当时用 Load) | **13833 ms**(79 版 / 2.77 GB 声明字节) | + +结论:接口超时不是锁、不是连接池,而是**按版本重复校验全部部件**导致的 O(全部版本字节)。 +79 个版本来自历次真实同步的预备份/派生快照,全部留在同一个归档根。 + +- 新增 `Archive.LoadManifest`:只校验清单自身(文件名摘要 == 内容摘要 + 结构), + 用于盘点/统计/列版本;`Load` 仍逐部件校验字节,同步/恢复路径不变。 +- `ContentUsage` 改用 `LoadManifest`(原先每个版本都 `Load`)。 + +- `GET /library/overview`、`GET /library/storage` 仍 **503**(10s 超时)。 + 说明还有别的调用点在枚举版本时用 `Load`(候选:最新封存选择、wiki 差异基线解析、保留策略扫描)。 + 需要继续把这些调用点逐个切到 `LoadManifest`,并用同样的分段计时验证。 + +改动范围:`internal/librarysnapshot/archive.go`、`internal/librarysnapshot/usage.go`、`CHANGELOG.md` +统计:3 个文件,+58/-1。 + +## 开发快照 snapshot/v0.1.145 - 2026-10-28 +目标提交:d1dbb2b9454836ce9d382981d25eccfa0c6cc152 + +### Changed +- **style: gofmt library_maintenance.go** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/platform/postgres/library_maintenance.go` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.144 - 2026-10-28 +目标提交:288e01a01aef1b3fe5c895b0fe9dc06b4b84d7f4 + +### Changed +- **feat(library): 目标维护窗口(持久所有权/独占/release-on-verify)+ known-broken 记录** +- 迁移 000070 `library_maintenance`:一个 scope 一个持有者,记录作业/目标身份/服务原状态/阶段/释放时间。 +- `AcquireLibraryMaintenance`(异主占用直接拒绝)、`UpdateLibraryMaintenanceState`、 + `ReleaseLibraryMaintenance`、`LibraryMaintenanceOf`。 +- worker:写目标前 acquire,写前置 `applying`,只有"验证通过 + 版本登记成功"才 release; + 回滚失败/回滚后未验证 → `needs_restore` 且保持持有(不会被 finally 释放)。 +- API:`GET /api/v1/library/maintenance`;同步入口在目标被他人持有时返回 409。 + +- 候选环境实测 `GET /api/v1/library/maintenance` 返回 **404**:路由已注册在 server.go:451, + 但运行实例没匹配上,根因未定位。 +- 维护窗口的真实 kill/接管取证未成功:取证脚本在 kill 之前作业已跑完(竞态), + 三项断言(持有中、kill 后仍持有、维护期间拒绝新写入)全部 false。 + 因此维护协议**没有真实证据**,不得声称已实现。 + +改动范围:`api/openapi.yaml`、`cmd/worker/library_handlers.go`、`cmd/worker/main.go`、`internal/httpapi/library_flow.go`、`internal/httpapi/server.go`、`internal/platform/postgres/library_maintenance.go` 等 10 个 +统计:10 个文件,+250/-6。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.143 - 2026-10-28 +目标提交:b76a433574b17c27607ca3146724652f73330264 + +### Fixed +- **fix(httpapi): 整库只读接口加请求级超时,避免归档大时请求挂死** +为什么:候选前端联调时 /library/overview 与 /library/storage 会挂到连接被重置, +浏览器无法渲染。加 10s 超时并返回明确降级原因,同时差异计算放进看门狗。 + +改动范围:`internal/httpapi/library_flow.go`、`internal/httpapi/server.go` +统计:2 个文件,+49/-3。 + +## 开发快照 snapshot/v0.1.142 - 2026-10-28 +目标提交:def6022048e9bf3273d99d8a2eddfc1178048421 + +### Docs +- **docs(ledger): 补记 0.1.137/0.1.138 并把进度文件更新到最终真实版本** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+5/-1。 + +## 开发快照 snapshot/v0.1.141 - 2026-10-28 +目标提交:e7ba2057aca11a5b56bff96156e1af1360a8b02a + +### Docs +- **docs(matrix): V03 标注架构阻塞、P02 真实计数、R01 恢复已验证** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-acceptance-matrix.md` +统计:1 个文件,+3/-3。 + +## 开发快照 snapshot/v0.1.140 - 2026-10-28 +目标提交:cef25d92a62ace37c5ecdeed07ba270dbe50c6cc + +### Changed +- **feat(worker): 四类进度用 manifest 真实总量,未知保持 NULL** +为什么:P02 要求分项计数/字节来自真实清单,不能永远 NULL 或写 0。 + +改动范围:`cmd/worker/library_handlers.go`、`cmd/worker/main.go` +统计:2 个文件,+49/-4。 + +## 开发快照 snapshot/v0.1.139 - 2026-10-28 +目标提交:f722ea87271a9d8d5bec465ee07f75bba4861d54 + +### Docs +- **docs(ledger): 补记 0.1.135 cancelled 与 0.1.136 success** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+5/-1。 + +## 开发快照 snapshot/v0.1.138 - 2026-10-28 +目标提交:e47e6d825dd21d883ae529e705dbc6306c411d7a + +### Docs +- **docs(matrix): 追加入口级 8 场景真实验收对照** +为什么:矩阵要与固定 SHA 的证据文件对应。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/library-acceptance-matrix.md` +统计:1 个文件,+16/-0。 + +## 开发快照 snapshot/v0.1.137 - 2026-10-28 +目标提交:7a2188a0ee7cd17c93142f25450128781c37a856 + +### Docs +- **docs(release): 入口级 8 场景真实验收证据与台账** +为什么:证据必须对应固定 SHA;旧证据与报告冲突的部分保留但不再引用。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+6/-1。 + +## 开发快照 snapshot/v0.1.136 - 2026-10-28 +目标提交:ea4cffbcaa54c0055ddcaeb812cb93a8ededbce3 + +### Changed +- **style: gofmt release_gate_test.go** +该提交未附正文,以下范围由真实 diff 归纳。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/libraryrelease/release_gate_test.go` +统计:1 个文件,+1/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryrelease/release_gate_test.go` + +## 开发快照 snapshot/v0.1.135 - 2026-10-28 +目标提交:c72b71d78afd1d72cefdc6ced301001172ccc968 + +### Fixed +- **fix(library): 预备份硬门禁 + 崩溃接管 + 五项入口验收** +- **预备份持久化成为硬门禁**:`BackupObserver.BackupPrepared` 现在返回 error, + 引用/版本登记失败时 `Run` 直接返回失败且**一个字节都不写目标**(此前只写日志继续写)。 +- 预备份同时登记成 `production_prebackup` 版本,失败与成功路径都留证据。 +- 中断接管按**队列租约**判定(原来只看 state=running,worker 刚被杀而租约未过期时会漏掉); + 运行期每 30 秒扫一次,不再只在启动时扫一次。 +- 接管用一次条件更新原子抢占(`running→pending` 只有一个 worker 成功),队列作业 ID/幂等键固定, + 并发接管只会得到同一条队列作业;重复接管时把 attempt 归零,否则 `attempt-queue` 会超长)。 +- 生产版本记录 ID 用稳定短摘要(`library_versions.id` 是 varchar(64),拼接完整快照 ID 会超长)。 +- 对象路径:真实 `resources.physical_path` 是 `storage:///oss:///`, + 写入/读取都先拆成裸 key;对象存储 provider 改为可配置(默认 s3),必须与生产 `resources.provider` 一致。 +- 生产索引不存在/为空时按"空基线"处理(Current/Export/Backup/Archive 全链路一致), + 非空时仍然严格比对映射,避免把索引丢失当成正常。 +- 索引校验按写入时的规范化比较(写入会补齐 tenant_id/knowledge_base_id), + 不一致时给出首个差异文档与字段。 + +- 收尾阶段 `finalize`:版本登记失败不再报成功,作业标失败并保留预备份,修好后可续作。 +- 崩溃接管:worker 启动时接管"running 但队列已无任务"的写作业,复用已持久化的预备份, + 绝不重新备份覆盖;没有预备份的判失败。 +- 跨 kind 互斥:迁移 000069 把唯一索引从 `(scope,kind)` 改成 `scope`(sync/restore 共用)。 +- 按版本恢复 `POST /library/restores`(管理员 + 该快照恢复授权)与续作 `POST /library/jobs/{id}/resume` + (管理员 + 闸口 + 授权,幂等键按源作业固定)。 +- 首次发布放行:生产可读且为空视为合法空基线;预备份不再作为启动前门禁(它由作业自身生成)。 + +- 环境:候选 API :18089 + 候选 worker + 独立治理库 `kg_candidate_1028165117` + + 真实 WeKnora 表结构的生产库 `kg_prod_1028165117` + 独立 ES 索引 + 独立 MinIO 桶。 +- 首次发布:生产为空 → 预备份 → 四类写入 → 校验 → 登记 `production_effective` 成功; + 真实落库 4 文档 / 38 分块 / 21 资源 / 31 Wiki 页 / 122 索引文档。 +- 同请求重复:返回同一作业且 `reused=true`,不重复写生产。 +- 未授权快照:503 并写明"没有与目标快照匹配的有效发布授权",不建作业。 +- 更新版本:第二份快照同步成功(真实改标题)。 +- 失败自动恢复:索引内容不一致、资源绑定行数不一致、对象上传失败等场景均被拒绝并回滚, + 回滚后生产回到上一版本(多次真实复现)。 +- 收尾失败不报成功:版本登记因 ID 超长失败时作业标 failed、生产写入保留并记录恢复责任。 + +改动范围:`cmd/api/main.go`、`cmd/library-e2e-seed/main.go`、`cmd/worker/library_handlers.go`、`cmd/worker/library_wiring.go`、`cmd/worker/main.go`、`internal/appconfig/config.go` 等 28 个 +统计:28 个文件,+878/-104。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_flow_test.go`、`internal/httpapi/library_restore_test.go`、`internal/platform/postgres/migrations_test.go`、`internal/releaseadapter/v071/library_production_isolation_test.go` + +## 开发快照 snapshot/v0.1.131 - 2026-10-28 +目标提交:8a68ea2da240134f5ac484d74412468048261473 + +### Docs +- **docs(release): 台账补记 0.1.129–0.1.130** +为什么:候选 SHA、CI 结论与真实服务验收事实要落台账。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+5/-1。 + +## 开发快照 snapshot/v0.1.130 - 2026-10-28 +目标提交:a2d051f6b3e7e0895eb4aaafdb941093536d82f5 + +### Changed +- **feat(library): 按版本恢复入口与失败作业续作** +- 迁移 000068:`library_jobs.kind` 允许 `restore`,与封存/同步分开。 +- `POST /api/v1/library/restores`:管理员按指定整库快照恢复;要求**针对该快照的恢复授权** + (同步授权不能顶替),闸口与恢复演练证据同样适用,缺项逐条拒绝且不建作业。 +- `POST /api/v1/library/jobs/{id}/resume`:失败/取消的同步或恢复作业按原参数重新排队, + 旧作业保留为审计记录;封存作业不走续作(由封存入口重发)。 +- worker 新增 `library.restore` 处理器,与同步共用"闸口 → 授权 → 预备份 → 四类写入 → 校验"链路; + 成功后登记 `production_effective` 版本(带预备份快照与字节数证据),失败/回滚失败分别留痕。 + +改动范围:`api/openapi.yaml`、`cmd/worker/library_handlers.go`、`cmd/worker/main.go`、`internal/governance/library_flow.go`、`internal/httpapi/library_restore.go`、`internal/httpapi/server.go` 等 11 个 +统计:11 个文件,+548/-91。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_restore_test.go`、`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.129 - 2026-10-28 +目标提交:0b9dbe12cfbb30584b2dd3dc1a726822cd5c7f4e + +### Fixed +- **fix(library): 资源范围精确关联 + 内容级校验 + 真实对象存储验收** +- 资源范围:`scopeWhere(resources)` 与 `resourcePaths` 现在用 `resource_bindings.resource_id = resources.id` + 精确关联,覆盖知识库绑定与分块绑定,并限制绑定租户;此前只按 owner_id 判断会命中整张资源表。 + 归档资源部件名与资源 ID 对齐(此前按 handle 查路径,导入会找不到对象)。 +- 资源路径查询补上 `provider`,对象读写按资源真实 provider 执行。 + +- `Verify` 由"行数 + ID 集合"升级为内容级:逐行规范化摘要(仅豁免 created_at/updated_at/processed_at/expires_at)、 + 归档索引文档与生产索引文档的规范摘要、以及每个资源对象的实际字节 SHA256。 + 同 ID 改正文、改 Wiki、改向量、损坏对象都会被拒绝。 +- 预备份一致性由行数升级为内容指纹:导出前后逐表比对"数量 + 内容摘要",同数量改写也会失败。 +- 本机真实对象存储(MinIO)纳入验收:`TestLibraryProductionIsolationRealServices` 用真实桶与对象, + 覆盖跨租户/跨库资源隔离、chunk-only 绑定、对象损坏、以及"源对象被改后仍能用预备份恢复"。 + +- 三条真实服务用例同时通过:隔离资源+内容校验(`kg_library_test_*` 库、随机 ES 索引、唯一 MinIO 桶)、 + 单阶段导入/撤销/恢复、完整编排 e2e。跑完删库、删索引、删对象。 + +改动范围:`internal/releaseadapter/v071/library_backup_store.go`、`internal/releaseadapter/v071/library_production_target.go`、`internal/releaseadapter/v071/library_verify_content.go`、`internal/releaseadapter/v071/s3_object_store.go`、`CHANGELOG.md`、`internal/releaseadapter/v071/library_production_isolation_test.go` 等 7 个 +统计:7 个文件,+677/-31。 + +### Tests +本次提交包含测试/夹具变更:`internal/releaseadapter/v071/library_production_isolation_test.go`、`internal/releaseadapter/v071/library_production_target_integration_test.go` + +## 开发快照 snapshot/v0.1.128 - 2026-10-28 +目标提交:6ca8481e8e8c22f401d3ef1cbea9e9b9dbe2de6f + +### Docs +- **docs(release): 台账补记 0.1.126 与真实服务验收结论** +为什么:候选 SHA 与 CI/验收结论要落在台账里,便于父审独立核对。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+3/-1。 + +## 开发快照 snapshot/v0.1.127 - 2026-10-28 +目标提交:8da4666e76f8b46cad29e41d2fdd0b98663c53bc + +### Tests +- **test(library): 端到端编排在本机真实 PG/ES 上跑通** +→ 独立预备份(626 字节)→ 注入故障(删行)→ 校验失败 → 恢复后校验通过。 +- `TestLibraryReleaseRunIsolated`:同一环境上跑完整编排(授权 → 预备份 → 数据库/资源/索引/Wiki → + 校验),返回 succeeded,预备份快照 8e4ed5ba…(1074 字节)。 + +改动范围:`scripts/test-scope.json`、`CHANGELOG.md`、`docs/release-ledger.md`、`internal/releaseadapter/v071/library_production_target_integration_test.go` +统计:4 个文件,+120/-5。 + +## 开发快照 snapshot/v0.1.126 - 2026-10-28 +目标提交:281699a43104af6ce5344b17375ac7102f3e563d + +### Changed +- **feat(library): 生产整库真实读写/校验/恢复与本机真实服务验收** +- `internal/releaseadapter/v071/library_production_target.go` 由占位改为真实实现: + `Current` 读生产每张知识域表的行数与「身份集合摘要」+ 索引条数;`Apply` 按阶段真实写入 + (database/wiki 同事务先删本库再插、resources 上传对象、index 批量索引原始文档与向量); + `Verify` 用身份摘要与索引条数比对;`Restore` 按预备份快照回放整条链路。 + 只触碰知识域表,不写 users/sessions/api_keys;不做解析或 Embedding。 +- 撤销真正落地:索引阶段在写入后用 `PruneScope` 删除"快照里没有"的文档,避免只增不减。 +- `library_backup_store.go`:写前独立完整预备份——生产 PG(RepeatableRead)+ ES 整库导出 + + 资源对象密封成归档版本,导出前后比对行数做静默检查;映射契约不符直接拒绝。 +- `search_write.go`:`Count`/`BulkIndex`/`PruneScope`,范围过滤统一按「租户 + 知识库」。 +- `cmd/worker/library_wiring.go`:用部署注入的配置装配生产目标与预备份;缺项时把缺的 + 环境变量名写进阶段并失败,不再使用空占位对象。 +- 配置:`LIBRARY_PRODUCTION_DATABASE_URL`、`LIBRARY_PRODUCTION_ELASTIC_URL/INDEX`、 + `LIBRARY_PRODUCTION_TENANT_ID`、`LIBRARY_PRODUCTION_KB_INTERNAL/EXTERNAL`、 + `LIBRARY_PRODUCTION_S3_*`、`LIBRARY_EMBEDDING_MODEL/DIMENSION`。 + +- `libraryretention.CheckCapacity` 用安全加法:int64 溢出直接拒绝,不再绕回负数通过门禁。 +- 保留策略组合语义写明为「每个已设维度都超限才成为候选」并提供 `policy_note`; + 锁定/备份恢复依赖通过 `ProtectedBy` 进入保护集合,永不候选。 + +- `TestLibraryProductionTargetIsolated` 在本机 WeKnora 测试栈真实 PG(一次性 `kg_library_test_*` 库) + 与真实 ES(随机索引)跑通:首次导入 → 校验 → 撤销传播(PG 行与 ES 文档都消失、跨租户不受影响) + → 独立预备份(3111e823…,626 字节)→ 注入故障(删行)→ 校验失败 → 恢复后校验通过。 + +改动范围:`cmd/worker/library_wiring.go`、`cmd/worker/main.go`、`internal/appconfig/config.go`、`internal/libraryrelease/release.go`、`internal/libraryretention/capacity.go`、`internal/libraryretention/retention.go` 等 16 个 +统计:16 个文件,+1521/-71。 + +### Tests +本次提交包含测试/夹具变更:`internal/libraryretention/retention_test.go`、`internal/releaseadapter/v071/library_production_target_integration_test.go`、`internal/releaseadapter/v071/library_production_target_test.go` + +## 开发快照 snapshot/v0.1.125 - 2026-10-28 +目标提交:444b8202108adc4309d6cc77da77eefe089fad71 + +### Docs +- **docs(release): 台账补记 0.1.123 与 run164 失败说明** +为什么:run164 的失败原因与修复要留在台账里,不能让失败消失。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+5/-1。 + +## 开发快照 snapshot/v0.1.124 - 2026-10-28 +目标提交:67a48faf23996b7cf55d2ed589e4235f9b4d4754 + +### Tests +- **test(qawemail): 去掉单测对外部 DNS 的依赖** +- `internal/qawemail` 单测把 `smtp.example.com` 当成可解析主机,构建机没有外网 DNS 时必红; + 改用 `localhost` 并新增"RFC 6761 保留域 .invalid 必须判非法"的确定性断言(不放宽校验)。 + +改动范围:`CHANGELOG.md`、`internal/qawemail/store_test.go` +统计:2 个文件,+18/-7。 + +## 开发快照 snapshot/v0.1.123 - 2026-10-28 +目标提交:7e3075f0fc77c51821925d7c7194c6c759678b87 + +### Docs +- **docs(release): 台账补记 0.1.121–0.1.122 与候选说明** +为什么:每个开发版本的 SHA 与 CI 结论要能独立审计,不留在交付报告里。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+4/-1。 + +## 开发快照 snapshot/v0.1.122 - 2026-10-28 +目标提交:2038462dbbd09b4bee00ea8d647c2bb0e94f538d + +### Changed +- **feat(library): Wiki 页面级差异与页面展示** +- `GET /library/overview` 的差异结果新增 `wiki`:两份快照 `wiki_pages` 的新增/更新/撤销页数与样例, + 明确标注页面级、不代表实体/边/引用重算;缺部件时如实 unknown。 + +改动范围:`internal/httpapi/library_flow.go`、`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/librarySnapshots.ts`、`CHANGELOG.md`、`docs/library-acceptance-matrix.md` +统计:6 个文件,+113/-1。 + +## 开发快照 snapshot/v0.1.121 - 2026-10-28 +目标提交:7b8cc0384b9160d488be6ed308b2007d9d6ef91f + +### Changed +- **feat(library): 生产发布授权与同步/恢复编排,worker 接线** +- `internal/libraryretention`:保留策略、依赖保护(生产生效/最近封存/预备份/在飞作业)与清理预览; + 预算未设置即不放行的容量门禁;已并入启动同步的缺项列表。 +- `GET /api/v1/library/storage`:真实归档用量 + 三类版本保护依据 + 策略 + 预览 + 容量,只读。 +- 发布授权:`library_release_authorizations`(目标+快照+参数摘要+有效期,可撤销), + `POST/GET /api/v1/library/authorizations`、`POST /{id}/revoke`,仅管理员签发,有效期 5–1440 分钟。 +- `internal/libraryrelease`:预备份 → 数据库/资源/索引/Wiki → 验证 → 失败恢复的编排, + 授权过期/目标不符/快照不符/参数变化一律在动生产前拒绝;恢复失败作为独立结论上报。 +- worker `library.sync` 接执行编排:先闸口、再授权,写入阶段落库;生产目标适配器未配置时明确失败。 +- 页面"生产保护与恢复"显示发布授权状态与有效期;系统设置→存储管理显示容量门禁、受保护版本与清理预览。 + +- 启动同步的缺项包含"没有与目标快照匹配的有效发布授权"与容量预算;作业只有在编排返回成功时才标成功。 + +改动范围:`api/openapi.yaml`、`cmd/worker/main.go`、`internal/governance/library_flow.go`、`internal/httpapi/library_authorization.go`、`internal/httpapi/library_flow.go`、`internal/httpapi/server.go` 等 22 个 +统计:22 个文件,+1289/-47。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_authorization_test.go`、`internal/httpapi/library_flow_test.go`、`internal/libraryrelease/release_test.go`、`internal/platform/postgres/migrations_test.go` 等 5 个 + +## 开发快照 snapshot/v0.1.120 - 2026-10-28 +目标提交:f91359f881077d1fad17fca021973c6beb54553f + +### Changed +- **feat(library): 存储管理真实化——容量门禁、依赖保护与清理预览** +为什么:存储页原来只有占用数字,保留/依赖保护/清理预览写"未接线";这些都不需要生产写权限, +按"未设置就不做决定"的口径实现成只读计算。 + +- internal/libraryretention:按保留数量/天数生成预览;生产生效、每库最近封存、每次生产预备份、 + 在飞作业引用一律受保护;回收预览只算独占字节,共享 blob 不计。 +- internal/libraryretention.CheckCapacity:预算未设置即不放行(无法证明装得下),超预算阻断; + 已并入 POST /library/syncs 的 gate 缺项。 +- 新增 GET /api/v1/library/storage:真实归档用量 + 三类版本保护依据 + 策略 + 预览 + 容量,只读。 +- 系统设置→存储管理改为读该接口:策略未设置时明确"全部保留、不生成候选",自动清理恒关闭。 +- 配置项 LIBRARY_CAPACITY_BUDGET_BYTES / LIBRARY_RETENTION_VERSIONS / LIBRARY_RETENTION_DAYS(未设=0)。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`internal/appconfig/config.go`、`internal/httpapi/library_flow.go`、`internal/httpapi/library_storage.go`、`internal/httpapi/server.go` 等 13 个 +统计:13 个文件,+756/-49。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/library_flow_test.go`、`internal/libraryretention/retention_test.go` + +## 开发快照 snapshot/v0.1.119 - 2026-10-28 +目标提交:12bb839d8a2cc138d3e4034d4dae559a52c3d094 + +### Docs +- **docs(release): 台账补记 0.1.116–0.1.118 的 CI 结论** +为什么:版本台账要能独立审计每个版本的 SHA 与 CI 结论,不能只留在交付报告里。 + +仅文档变更,不改变运行时行为。 + +改动范围:`docs/release-ledger.md` +统计:1 个文件,+5/-0。 + +## 开发快照 snapshot/v0.1.118 - 2026-10-28 +目标提交:3ad48ff32cf301b70555e93409d8593b3b571be0 + +### Changed +- **feat(web): 版本抽屉分列三类版本,不再只写"生产未接入"** +为什么:抽屉原来把所有记录都当测试封存,生产生效与本次预备份的真实状态读不到; +overview 已有三类版本,抽屉直接展示,缺的写"无记录",不互相顶替。 + +改动范围:`web/src/ReleaseTargets.tsx`、`web/src/releaseTargets.css` +统计:2 个文件,+49/-1。 + +## 开发快照 snapshot/v0.1.117 - 2026-10-28 +目标提交:e313ae12f29bd5579f779122cf6a26a87a98a87d + +### Changed +- **feat(library): 真实差异比对 + 孤儿作业回收 + 验收矩阵与台账** +- `internal/librarydiff`:按稳定文档 ID 与"排除 updated_at/updated_by 的整行摘要"比较两份归档快照的 `knowledges`, + 输出新增/更新/撤销;`GET /api/v1/library/overview` 在生产权威版本与测试封存都存在时给出真实差异, + 并标注 `base_snapshot`/`target_snapshot`,缺任一侧仍为 unknown 且说明原因。 +- 发布管理"本次变更"按文档身份列出增改撤销,并显示比对的两个版本前缀。 +- `ReconcileLibraryJobs`:worker 启动时把"队列里已无对应作业"的孤儿业务作业标失败并写明恢复责任, + 避免永久占用 (scope,kind) 互斥索引。 +- `docs/library-acceptance-matrix.md`:30 个验收项逐条对应代码/证据/状态(已实现已验证、已实现未验证、阻塞、未实现)。 +- `docs/release-ledger.md`:版本→完整 SHA→CI run 与结果→是否部署 的台账。 + +- 格式问题(CI run149–156 在 format 步骤连续失败):按仓库既有 prettier 配置格式化四个文件,不放宽检查。 +- `TestEmbeddedMigrationsAreVersionSorted` 仍写死 65 条迁移,新增 000066 后必红;改为 66 并补 000066 内容断言。 + +改动范围:`cmd/worker/main.go`、`internal/httpapi/library_flow.go`、`internal/librarydiff/diff.go`、`internal/platform/postgres/library_jobs.go`、`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx` 等 12 个 +统计:12 个文件,+490/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/librarydiff/diff_test.go` + +## 开发快照 snapshot/v0.1.116 - 2026-10-28 +目标提交:e3db157ba8d78f6046114055a24f7812e224e576 + +### Tests +- **test(postgres): 迁移清单跟随 000066 更新** +为什么:新增 000066 整库作业迁移后,清单测试仍写死 65 条,CI tests-go 因此红。 +不只改计数,同时断言 66 建出 library_versions 与 library_jobs_active 互斥索引。 + +仅测试/夹具变更,不新增产品或运行时功能。 + +改动范围:`internal/platform/postgres/migrations_test.go` +统计:1 个文件,+7/-2。 + +## 开发快照 snapshot/v0.1.115 - 2026-10-28 +目标提交:7e1610da44387c1cd86af2605311997e8249c47a + +### Changed +- **style(web): 格式化发布管理相关文件** +为什么:远端 CI run149–155 连续在 format 步骤失败,报的就是这四个新文件; +不做格式白名单、不跳过检查,直接按仓库既有 prettier 配置格式化。 + +改动范围:`web/src/LibraryStorageSection.tsx`、`web/src/ReleaseTargets.test.tsx`、`web/src/ReleaseTargets.tsx`、`web/src/releaseTargets.css` +统计:4 个文件,+55/-29。 + +## 开发快照 snapshot/v0.1.106 - 2026-10-28 +目标提交:9f990158b97c0f6d0ec010b01a80f540042d5945 + +### Tests +- **test(snapshot): 联合验证真实隔离存储与暂停恢复链** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`scripts/test-scope.json`、`docs/library-sync-acceptance.md`、`internal/releaseadapter/v071/library_capture_integration_test.go` +统计:3 个文件,+258/-0。 + +## 开发快照 snapshot/v0.1.95 - 2026-10-27 +目标提交:74aef1e373513ec605750313b7002528575bf50e + +### Fixed +- **fix(fetch): 修复钉钉来源身份与取件失败监控** +- 修复钉钉新登记来源仍按旧离线目录身份取件的问题:从当前来源配置读取空间坐标,实时枚举所在目录并严格匹配节点身份;同名文件不替代、目录不完整不误报删除。修复解析监控遗漏 `version_id` 取件任务及缺少业务元数据的失败记录,正确显示失败原因、重试入口与历史。 + +改动范围:`internal/platform/postgres/artifact_fetch_store.go`、`internal/platform/postgres/monitor_tasks.go`、`internal/workflows/artifactfetch/handler.go`、`scripts/artifact_fetch.py`、`scripts/artifact_fetch_test.py`、`CHANGELOG.md` 等 8 个 +统计:8 个文件,+211/-9。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/artifact_fetch_manifest_integration_test.go`、`internal/platform/postgres/monitor_tasks_integration_test.go` + +## 开发快照 snapshot/v0.1.94 - 2026-10-27 +目标提交:1b09f7d35bcb7a4b2a79417996933b1b45a31c95 + +### Changed +- **feat(monitor): 卡片概况与统一文件处理表** +- 新增解析监控统一工作台:顶部运行指标与依赖卡片、下方单一文件处理表;来源文件与目标测试库归并最新版本,动作、阶段、状态分列,后端统一筛选、计数及分页。支持单条/跨页勾选重试和版本、失败、重试详情;测试库移除重试独立核验治理规则,不误走上传流程。取消六分页和手动开始入口,生产授权仍在发布管理;本次不改变后台自动调度策略,自动处理闭环需另行端到端验收。 + +改动范围:`api/openapi.yaml`、`internal/httpapi/monitor_tasks.go`、`internal/httpapi/server.go`、`internal/platform/postgres/monitor_removal_retry.go`、`internal/platform/postgres/monitor_tasks.go`、`web/src/WeKnoraMonitor.test.tsx` 等 13 个 +统计:13 个文件,+2224/-545。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/monitor_tasks_test.go`、`internal/platform/postgres/monitor_tasks_integration_test.go` + +## 开发快照 snapshot/v0.1.93 - 2026-10-27 +目标提交:d8d143eca004b83cbd9226f088ddf8ad79f2317f + +### Changed +- **style(catalog): 移除冗余策略说明和大小展示** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/SourceCatalogBrowser.test.tsx`、`web/src/SourceCatalogBrowser.tsx`、`web/src/sourceCatalogBrowser.css` +统计:3 个文件,+6/-7。 + +## 开发快照 snapshot/v0.1.92 - 2026-10-27 +目标提交:52f411783581f3aa586ff86838ca99ba69b1b251 + +### Changed +- **style(catalog): 文件格式筛选默认折叠** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/SourceCatalogBrowser.test.tsx`、`web/src/SourceCatalogBrowser.tsx`、`web/src/sourceCatalogBrowser.css` +统计:3 个文件,+7/-0。 + +## 开发快照 snapshot/v0.1.91 - 2026-10-27 +目标提交:7448d130ec71cbfd4f31b9a2eff4bc8ef7b57f2e + +### Changed +- **feat(settings): 汇总全部数据源配置操作审计** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`api/openapi.yaml`、`internal/httpapi/auth.go`、`internal/httpapi/dingtalk_session.go`、`internal/httpapi/server.go`、`internal/httpapi/source_audit.go`、`internal/platform/postgres/dingtalk_space.go` 等 17 个 +统计:17 个文件,+453/-56。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/source_audit_test.go`、`internal/platform/postgres/migrations_test.go`、`internal/platform/postgres/source_audit_integration_test.go` + +## 开发快照 snapshot/v0.1.90 - 2026-10-27 +目标提交:f0b12412a1fa6cd7251692cc485369c3dd789f1c + +### Fixed +- **fix(settings): 仅展示只读DWS凭证状态** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`internal/dingtalkdiscovery/credentials.go`、`internal/httpapi/dws_credentials.go`、`internal/httpapi/server.go`、`web/src/SourceManagement.tsx` 等 8 个 +统计:8 个文件,+149/-32。 + +### Tests +本次提交包含测试/夹具变更:`internal/dingtalkdiscovery/credentials_test.go` + +## 开发快照 snapshot/v0.1.89 - 2026-10-27 +目标提交:5b6e154459d510a26e8434ad521f5009b803a0a1 + +### Changed +- **feat(settings): 增加连接移除确认并收拢数据源设置** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/SourceManagement.css`、`web/src/SourceManagement.tsx`、`web/src/SystemSettings.tsx` +统计:3 个文件,+112/-16。 + +## 开发快照 snapshot/v0.1.88 - 2026-10-27 +目标提交:8930ef29cdfd091dfb0897c195799fba002e12bd + +### Changed +- **feat(settings): 集中刷新策略与账号授权并独立设置审计** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/DingtalkSessionControl.tsx`、`web/src/SourceManagement.css`、`web/src/SourceManagement.test.tsx`、`web/src/SourceManagement.tsx`、`web/src/SystemSettings.test.tsx`、`web/src/SystemSettings.tsx` 等 8 个 +统计:8 个文件,+520/-214。 + +## 开发快照 snapshot/v0.1.87 - 2026-10-26 +目标提交:a55d7f454e74f3a123b62180adad90a7ab16868b + +### Changed +- **style(settings): 精简数据源列表并收起低频管理操作** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/SystemSettings.test.tsx`、`web/src/SystemSettings.tsx`、`web/src/systemSettings.css` +统计:3 个文件,+119/-78。 + +## 开发快照 snapshot/v0.1.86 - 2026-10-26 +目标提交:b55292bba16666fd2a7b20c1f06fd3a440781bc0 + +### Fixed +- **fix(catalog): 重复点击当前来源时保留已加载目录** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`web/src/SourceCatalogBrowser.tsx` +统计:1 个文件,+1/-1。 + +## 开发快照 snapshot/v0.1.85 - 2026-10-26 +目标提交:02ee5da231d8f761a756db06aad4ab4b3a225767 + +### Fixed +- **fix(catalog): 保留首次扫描零进度避免来源列表解析失败** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/governance/source_catalog.go`、`internal/governance/source_catalog_json_test.go` +统计:2 个文件,+22/-1。 + +### Tests +本次提交包含测试/夹具变更:`internal/governance/source_catalog_json_test.go` + +## 开发快照 snapshot/v0.1.84 - 2026-10-26 +目标提交:e744114ff438cbbf15d98dd46a5e74f900d0b1be + +### Fixed +- **fix(catalog): 打通共享授权扫描与统一来源清单显示** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/httpapi/dingtalk_session.go`、`internal/workflows/catalogrefresh/handler.go`、`web/src/SystemSettings.test.tsx`、`web/src/SystemSettings.tsx`、`scripts/dws_dingtalk_scan.py`、`scripts/live_catalog_refresh.py` 等 10 个 +统计:10 个文件,+276/-50。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/dingtalk_register_dws_test.go`、`internal/workflows/catalogrefresh/handler_test.go` + +## 开发快照 snapshot/v0.1.83 - 2026-10-26 +目标提交:c8f05ee46f61ee2ec6a8f20a31fea5783a61b283 + +### Fixed +- **fix(qa): 数据源登记改走 dws,不再要求扫码** +侧车扫码会话只用于解析空间;dws 已有有效凭据且能给出空间 id 与根节点, +登记直接用 dws 结果生成确定性键,同名来源回 200 already_present,认不出的空间才回落侧车。 + +改动范围:`internal/httpapi/dingtalk_session.go`、`internal/httpapi/dingtalk_register_dws_test.go` +统计:2 个文件,+123/-0。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/dingtalk_register_dws_test.go` + +## 开发快照 snapshot/v0.1.82 - 2026-10-26 +目标提交:1e8d8e3cdf30d94fad4b99ce1e5ede3da5d402fd + +### Fixed +- **fix(qa): 数据源配置去掉页面噪声** +去掉页头副标题与刷新状态按钮、工程师/管理员权限条、活动版本横幅、 +与向导重复的模块说明,以及「去掉来源」按钮(保留停用来源与修订历史)。 + +改动范围:`web/src/SourceWizard.tsx`、`web/src/SystemSettings.test.tsx`、`web/src/SystemSettings.tsx` +统计:3 个文件,+14/-98。 + +## 开发快照 snapshot/v0.1.81 - 2026-10-26 +目标提交:842bfccc1b92613212589b66db39a24ad29975ed + +### Changed +- **feat(qa): 向导补上 Partner Portal 卡片** +PP 一个门户一条来源:已接入时只做说明不重复登记,未接入时提示需服务端放置只读凭据。 + +改动范围:`web/src/SourceWizard.tsx`、`web/src/SystemSettings.tsx` +统计:2 个文件,+15/-4。 + +## 开发快照 snapshot/v0.1.80 - 2026-10-26 +目标提交:740f0e69baf33f64b9dec7b5689f56cd3159baa6 + +### Changed +- **feat(qa): 数据源配置改为统一添加向导** +一个「+ 添加数据源」入口、五步固定(类型→授权→范围→校验→保存),类型只换候选来源与授权说明, +版式与动作词一致;对内/对外不在此配置,仍只在来源与目录标注。钉钉两类复用既有登记与扫码会话路径。 + +改动范围:`web/src/SourceWizard.tsx`、`web/src/SystemSettings.test.tsx`、`web/src/SystemSettings.tsx`、`web/src/sourceWizard.css` +统计:4 个文件,+468/-7。 + +## 开发快照 snapshot/v0.1.79 - 2026-10-26 +目标提交:020cae1228d2c5a30808baadf42444fdd91a3236 + +### Changed +- **style: gofmt 新增的发现与路由文件** +该提交未附正文,以下范围由真实 diff 归纳。 + +改动范围:`internal/dingtalkdiscovery/docs.go`、`internal/dingtalkdiscovery/docs_test.go`、`internal/httpapi/source_available_test.go` +统计:3 个文件,+4/-4。 + +### Tests +本次提交包含测试/夹具变更:`internal/dingtalkdiscovery/docs_test.go`、`internal/httpapi/source_available_test.go` + +## 开发快照 snapshot/v0.1.78 - 2026-10-26 +目标提交:017cbcb851901c1ad0a7571aaea211fafae0ace7 + +### Changed +- **feat(api): 数据源统一列举与添加前校验** +GET /source-catalog/available 四类同一形状(wiki/drive/doc/pp),类型差异只放在 content_type; +POST /source-catalog/validate 真读一次:文档表格校验可读、知识库钉盘校验账号仍可见,失败不让添加。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`internal/httpapi/server.go`、`internal/httpapi/source_available.go`、`internal/httpapi/source_available_test.go` +统计:5 个文件,+423/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/httpapi/source_available_test.go` + +## 开发快照 snapshot/v0.1.77 - 2026-10-26 +目标提交:b3992bfa0f5c61cdb102393d8fba12b9cae58308 + +### Changed +- **feat(discovery): 钉钉在线文档与表格的发现与可读校验** +数据源统一改造第一块:drive +recent/+search-docs 列文档与表格,doc info / sheet list 真读一次 +校验可读;表格按 contentType 分流,读不到返回 ErrUnavailable 而不是空列表。 + +改动范围:`internal/dingtalkdiscovery/docs.go`、`internal/dingtalkdiscovery/docs_test.go` +统计:2 个文件,+267/-0。 + +### Tests +本次提交包含测试/夹具变更:`internal/dingtalkdiscovery/docs_test.go` + +## 开发快照 snapshot/v0.1.76 - 2026-10-26 +目标提交:2902ba0af2dbb01bbab49dee5662f23833d74d56 + +### Changed +- **feat(qa): 周报页移除下载 MD 与下载明细 CSV 按钮** +保留复制排版内容与自动保存;列表页的完整明细导出不受影响。 + +改动范围:`web/src/QAWeekly.tsx`、`web/src/QAWeeklyEditor.test.tsx` +统计:2 个文件,+4/-40。 + +## 开发快照 snapshot/v0.1.75 - 2026-10-26 +目标提交:555bc6ff5614a672d82ff5c103f92da04b86bb93 + +### Changed +- **feat(qa): 确认周报时自动附上统计看板图片** +看板图只能由浏览器渲染,改为确认动作内自动生成并上传,不再要求另点按钮;只在已附图时提供重新生成。 + +改动范围:`web/src/QAWeeklyMail.tsx` +统计:1 个文件,+12/-3。 + +## 开发快照 snapshot/v0.1.74 - 2026-10-26 +目标提交:1dda055e4c5ea2b0556141ee5efac7de2ba2e526 + +### Fixed +- **fix(qa): 授权码加密不再绑定设置版本** +AAD 只含租户,避免任何设置变更使已存密码失效;旧行在解密时按历史版本回退,无需重填授权码。 + +改动范围:`internal/qawemail/store.go` +统计:1 个文件,+15/-5。 + +## 开发快照 snapshot/v0.1.73 - 2026-10-26 +目标提交:f87d3fad9be11f1164137d1e28f2599dccab43fd + +### Fixed +- **fix(qa): 授权码按写入版本解密,改其它设置不再失效** +只调整收件人或时间也会递增设置版本,导致已保存的授权码无法解密、测试发送报存储不可用。 +新增 password_revision 记录加密时的版本,迁移回填现有行。 + +改动范围:`internal/platform/postgres/migrations/000064_weekly_mail_password_revision.down.sql`、`internal/platform/postgres/migrations/000064_weekly_mail_password_revision.up.sql`、`internal/qawemail/store.go`、`internal/platform/postgres/migrations_test.go` +统计:4 个文件,+20/-10。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/migrations_test.go` + +## 开发快照 snapshot/v0.1.72 - 2026-10-26 +目标提交:23b19350cce09a18a52ccfa9d0e66e9b2c2539a6 + +### Changed +- **feat(qa): 周报邮件新增测试/审核邮箱** +测试发送只投递到该地址,不打扰主送与抄送名单;未填时退回主送首位。 + +改动范围:`api/openapi.yaml`、`internal/platform/postgres/migrations/000063_weekly_mail_review_address.down.sql`、`internal/platform/postgres/migrations/000063_weekly_mail_review_address.up.sql`、`internal/qawemail/store.go`、`web/src/QAWeeklyMail.tsx`、`web/src/qaWeeklyMail.test.tsx` 等 9 个 +统计:9 个文件,+49/-10。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/migrations_test.go`、`internal/qawemail/store_test.go` + +## 开发快照 snapshot/v0.1.71 - 2026-10-26 +目标提交:1a9d1fff8391e531b0af6b6b32f0ae0ee86e057a + +### Fixed +- **fix(qa): 看板附图上传改用大请求体解码** +附图 base64 远超 64KiB 的质量编辑上限,被误判为无效设置并报“发送设置无效”。 +改为 12MB 上限的通用解码,并返回明确的图片错误文案。 + +改动范围:`internal/httpapi/qa_weekly_mail.go` +统计:1 个文件,+3/-2。 + +## 开发快照 snapshot/v0.1.70 - 2026-10-26 +目标提交:822fb652ffaea9088e31f87ad1abf5a8b4fdc3ff + +### Changed +- **feat(qa): 周报邮件区分主送抄送并附统计看板图片** +设置新增抄送列表,MIME 增加 Cc 头、envelope 同时投递主送与抄送,同一地址不得同时出现在两边。 +确认后可保存统计看板 PNG(multipart/related + cid),发送时放在正文末尾内联显示;正文改动使确认失效后不再使用旧图。 + +改动范围:`api/openapi.yaml`、`internal/httpapi/qa_weekly_mail.go`、`internal/httpapi/server.go`、`internal/platform/postgres/migrations/000061_weekly_mail_cc.down.sql`、`internal/platform/postgres/migrations/000061_weekly_mail_cc.up.sql`、`internal/platform/postgres/migrations/000062_weekly_mail_dashboard_image.down.sql` 等 13 个 +统计:13 个文件,+325/-25。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/migrations_test.go`、`internal/qawemail/store_test.go` + +## 开发快照 snapshot/v0.1.69 - 2026-10-26 +目标提交:600e9a2dc2464d0013f40375d71ab3937ce4e401 + +### Changed +- **feat(qa): 周报邮件支持立即发送测试** +管理员可在周报页先发一封测试邮件验证邮箱配置;测试不写入发送记录、不改变确认状态,也不会把该周标记为已发送。 + +改动范围:`api/openapi.yaml`、`internal/httpapi/qa_weekly_mail.go`、`internal/httpapi/server.go`、`internal/qawemail/store.go`、`web/src/QAWeeklyMail.tsx`、`web/src/qaWeeklyMail.ts` +统计:6 个文件,+106/-3。 + +## 开发快照 snapshot/v0.1.68 - 2026-10-26 +目标提交:eed83039ab08c7ed88a6bf2c01c3d1f0ad299bdb + +### Fixed +- **fix(qa): 邮件设置未配置时返回空收件人数组** +未初始化设置时 recipients 为 null,前端严格解析判为无效,导致周报页显示“响应无效”。 +后端统一返回空数组,前端同时兼容历史的 null。 + +改动范围:`internal/qawemail/store.go`、`web/src/qaWeeklyMail.ts` +统计:2 个文件,+7/-3。 + +## 开发快照 snapshot/v0.1.67 - 2026-10-26 +目标提交:43b1e423fec01a3534dac4dfba9b6152d4bab36c + +### Fixed +- **fix(qa): 规范化邮件调度测试并声明其外部依赖** +gofmt 修正;该集成测试需要一次性库与测试密钥,按仓库约定在 test-scope.json 声明为可选未运行。 + +改动范围:`scripts/test-scope.json`、`internal/qawemail/scheduling_integration_test.go` +统计:2 个文件,+15/-3。 + +### Tests +本次提交包含测试/夹具变更:`internal/qawemail/scheduling_integration_test.go` + +## 开发快照 snapshot/v0.1.66 - 2026-10-26 +目标提交:5aa32492de54c9ac32841e8fef96489697611d10 + +### Fixed +- **fix(qa): 周报邮件读取鉴权与测试隔离** +只读查询不再要求同源操作意图,写操作仍需;前端改为直接消费已解包的 data。 +调度集成测试按租户清理并要求一次性测试库,避免误清正式数据。 + +改动范围:`internal/httpapi/qa_weekly_mail.go`、`web/src/qaWeeklyMail.ts`、`internal/qawemail/scheduling_integration_test.go` +统计:3 个文件,+139/-8。 + +### Tests +本次提交包含测试/夹具变更:`internal/qawemail/scheduling_integration_test.go` + +## 开发快照 snapshot/v0.1.65 - 2026-10-26 +目标提交:68162895528ef3a1f42be18693d32915fc3abfde + +### Changed +- **feat(qa): 周报确认后按设定时段自动发邮件** +周报页新增自动发送区:管理员配置 SMTP/收件人/发送星期与小时,授权码用主密钥加密保存。 +确认绑定当前草稿版本与正文哈希,正文再改则确认失效;worker 每分钟仅在启用、到点、周已结束且未发送过时发一次。 +邮件同时提供渲染后的 HTML 与可读纯文本;SMTP 仅允许 TLS,拒绝明文。 +迁移 000060 新增设置、确认与发送记录三张表,唯一键防重复发送。 + +改动范围:`api/openapi.yaml`、`cmd/api/main.go`、`cmd/worker/main.go`、`cmd/worker/qa_weekly_mail.go`、`internal/httpapi/qa_weekly_mail.go`、`internal/httpapi/server.go` 等 18 个 +统计:18 个文件,+1550/-2。 + +### Tests +本次提交包含测试/夹具变更:`internal/platform/postgres/migrations_test.go`、`internal/qawemail/store_test.go` + +## 开发快照 snapshot/v0.1.64 - 2026-10-25 +目标提交:1d33ff697632c8ab81ce2ae305ed200ef1fd2da6 + +### Fixed +- **fix(qa): 无法判定的判错结论不再计入正确率分母** +根因为“结果不清”或未归因的判错记录移出正确率分母,并以 unjudged 单独返回,覆盖率口径不变。 +统计规则版本升到 calendar-month-strict-yes-over-yes-no-v2,前端严格解析同步接受该字段。 + +改动范围:`api/openapi.yaml`、`internal/qaopsmirror/dashboard_monthly.go`、`web/src/qaDashboardData.ts`、`internal/qaopsmirror/dashboard_monthly_test.go`、`web/src/qaDashboardMonthly.fixture.ts` +统计:5 个文件,+74/-11。 + +### Tests +本次提交包含测试/夹具变更:`internal/qaopsmirror/dashboard_monthly_test.go`、`web/src/qaDashboardMonthly.fixture.ts` + +## 开发快照 snapshot/v0.1.35 - 2026-10-19 +目标提交:330fd12b3f30376c3aee757ba2a9f4ba3f8bcf27 + +### Changed +- **Merge pull request 'Feat/qa ops query' (#9) from feat/qa-ops-query into main** +Reviewed-on: https://gitea.zheromiku.top/Weknora/knowledge-governance/pulls/9 + +合并提交:以上仅按第一父提交的 diff 边界归纳,不把并行分支改动重复计入。 + diff --git a/internal/versionhistory/development_notes_history_test.go b/internal/versionhistory/development_notes_history_test.go new file mode 100644 index 0000000..bf6628c --- /dev/null +++ b/internal/versionhistory/development_notes_history_test.go @@ -0,0 +1,54 @@ +package versionhistory + +import ( + "encoding/json" + "os" + "strings" + "testing" + + "knowledge-governance/internal/governance" +) + +func TestAllHistoricalDevelopmentNotesBindExactCommit(t *testing.T) { + changelogBytes, err := os.ReadFile("../../CHANGELOG.md") + if err != nil { + t.Fatal(err) + } + changelog := string(changelogBytes) + fixtureBytes, err := os.ReadFile("testdata/development_notes_history.json") + if err != nil { + t.Fatal(err) + } + var entries []struct { + Version string `json:"version"` + SHA string `json:"sha"` + } + if err := json.Unmarshal(fixtureBytes, &entries); err != nil { + t.Fatal(err) + } + if len(entries) != 270 { + t.Fatalf("history fixture must contain 270 missing development versions, got %d", len(entries)) + } + seen := map[string]string{} + wrong := strings.Repeat("f", 40) + for _, entry := range entries { + if prev, ok := seen[entry.Version]; ok && prev != entry.SHA { + t.Fatalf("version %s bound to multiple commits", entry.Version) + } + seen[entry.Version] = entry.SHA + notes := governance.ExtractDevelopmentNotesForCommit(changelog, entry.Version, entry.SHA) + if strings.TrimSpace(notes) == "" { + t.Fatalf("version %s sha %s has no exact-SHA notes", entry.Version, entry.SHA) + } + if strings.Contains(notes, "目标提交:"+entry.SHA) == false { + t.Fatalf("version %s body does not carry its exact target line", entry.Version) + } + if leaked := governance.ExtractDevelopmentNotesForCommit(changelog, entry.Version, wrong); leaked != "" { + t.Fatalf("version %s notes leaked to wrong SHA: %q", entry.Version, leaked) + } + } + // 相邻/前缀版本不能互相命中。 + if notes := governance.ExtractDevelopmentNotesForCommit(changelog, "0.1.35", entries[0].SHA); notes != "" { + t.Fatalf("0.1.35 leaked notes for another commit: %q", notes) + } +} diff --git a/internal/versionhistory/development_notes_plain_test.go b/internal/versionhistory/development_notes_plain_test.go index c91a7f2..afbc003 100644 --- a/internal/versionhistory/development_notes_plain_test.go +++ b/internal/versionhistory/development_notes_plain_test.go @@ -70,8 +70,8 @@ func TestRealChangelogDevelopmentNotes(t *testing.T) { if notes == "" || !strings.Contains(notes, tc.want) { t.Fatalf("real CHANGELOG %s notes missing: %q", tc.version, notes) } - if governance.ExtractDevelopmentNotes(changelog, "0.1.35") != "" { - t.Fatal("prefix version matched a longer version") + if other := governance.ExtractDevelopmentNotes(changelog, "0.1.35"); strings.Contains(other, "首次同步可复用") || strings.Contains(other, "索引预检") { + t.Fatalf("0.1.35 matched a longer version's body: %q", other) } } } diff --git a/internal/versionhistory/testdata/development_notes_history.json b/internal/versionhistory/testdata/development_notes_history.json new file mode 100644 index 0000000..8c978b0 --- /dev/null +++ b/internal/versionhistory/testdata/development_notes_history.json @@ -0,0 +1,1082 @@ +[ + { + "version": "0.1.349", + "sha": "0d9091e4644cf3eb053fe2c852bdbab90a51cc24" + }, + { + "version": "0.1.348", + "sha": "e0fc2c32b89b7ac74f2346057b43436f9404ba3a" + }, + { + "version": "0.1.347", + "sha": "604295549840dd2f4fbe2f91f4ac74d529cc5ca2" + }, + { + "version": "0.1.346", + "sha": "a55319d9726f063baf43b7fe1e3fb7768946a4d8" + }, + { + "version": "0.1.345", + "sha": "017765763a74919fdcc0591454ae85d3415f6a43" + }, + { + "version": "0.1.344", + "sha": "bd452e85b3b40083ac13c35cd9afa04dd978c714" + }, + { + "version": "0.1.343", + "sha": "71f23c67639f3ea8f0171a56d0198ac815ca216a" + }, + { + "version": "0.1.342", + "sha": "b4add6c9b0912294ceb9e00a8d54a4bd955f9135" + }, + { + "version": "0.1.341", + "sha": "f9457fab52eb84c7af0edad492aad23fa59658ea" + }, + { + "version": "0.1.340", + "sha": "5e1482e349e23728f8f2f1aeb770422eba2e5b5f" + }, + { + "version": "0.1.339", + "sha": "4421dd38cec74fcfcdde177e65238a9ca38c42e5" + }, + { + "version": "0.1.338", + "sha": "22fb33aa7573f56903a588bf6ca27b9d01e8a006" + }, + { + "version": "0.1.337", + "sha": "0d180aa401e388667b01475f497e5afc91396ccf" + }, + { + "version": "0.1.336", + "sha": "0a87dc8e037c4d7c61b8c566e9e9ca10e8b62308" + }, + { + "version": "0.1.335", + "sha": "96472c1ddee829b58e7daf5f099a852c666b1b33" + }, + { + "version": "0.1.334", + "sha": "bad9034cf76e69c5b11009b469c5f5ca48a9433f" + }, + { + "version": "0.1.333", + "sha": "f330a75641cc1a69c1253458b079e69419709384" + }, + { + "version": "0.1.332", + "sha": "19ec03c4171e8700425c01d960d1ba5ae17c3c40" + }, + { + "version": "0.1.331", + "sha": "6209072e43172c4d52c595b9330c6c7fbab2615a" + }, + { + "version": "0.1.330", + "sha": "e61f05c98cfd2cef067f3303965b543097d733eb" + }, + { + "version": "0.1.329", + "sha": "a9d5adf78f1213ca9f058588ae3f4ba4b040f41e" + }, + { + "version": "0.1.328", + "sha": "e8f80694735dc12db4412a2c3f6724f390e87517" + }, + { + "version": "0.1.327", + "sha": "b7d6661a4e774721c05f3cb085fcb87e4658d15e" + }, + { + "version": "0.1.326", + "sha": "97da6ceba86862703a6ed848d511161711bb5b88" + }, + { + "version": "0.1.325", + "sha": "7cdbb74c8fd75c76abeb480516781e8c98713ef5" + }, + { + "version": "0.1.324", + "sha": "1aa60c159ebaac6c47799cd41bca6931ea3f85a1" + }, + { + "version": "0.1.323", + "sha": "245cefd6b0206827ba99ff88f92b206ef45cc335" + }, + { + "version": "0.1.322", + "sha": "1e2bb385c77f3fe3b93152922e7666736a356936" + }, + { + "version": "0.1.321", + "sha": "bf7f5fa03c75752f237735ada53e808e3d19a397" + }, + { + "version": "0.1.320", + "sha": "24608e132bcc5026d8a305e545c69347533a595c" + }, + { + "version": "0.1.319", + "sha": "80254756f1e0ef604b448ab98d7fe02f8c0e80dd" + }, + { + "version": "0.1.318", + "sha": "37f2dc73f92b1c2f8d826315ac19743ec640fae5" + }, + { + "version": "0.1.317", + "sha": "9e31a6f95641b78c577a9aa4b70c5956ddef2afd" + }, + { + "version": "0.1.316", + "sha": "1c58f1ef7eb43c0848626d965420901906602af4" + }, + { + "version": "0.1.315", + "sha": "3b7ad9061c7068980971c7fc4a66cd3b03198a8f" + }, + { + "version": "0.1.314", + "sha": "882e38591d919886446b49523ad315678c6eddc6" + }, + { + "version": "0.1.313", + "sha": "a1112d368a127ab73f706617200182ec47ed5e7a" + }, + { + "version": "0.1.312", + "sha": "ca32f2e661d06f9b2dd76b6f1ba4a6086dbf5364" + }, + { + "version": "0.1.311", + "sha": "513aa9e6f5e87b916583fa1d3d418363f547dcfd" + }, + { + "version": "0.1.310", + "sha": "3bce6c7a3fb1b870c4dcbca554b1ba75f464e5f9" + }, + { + "version": "0.1.309", + "sha": "18a4b83f7844589fced0d7d86b5897b2d945e3b7" + }, + { + "version": "0.1.308", + "sha": "7b2ab642f7788466c8bb0cdf662c9c49825483cf" + }, + { + "version": "0.1.307", + "sha": "0a7c9f85105657ed91e9bf0a156952dfc8d36947" + }, + { + "version": "0.1.306", + "sha": "e09fddcb74b49cea56256106e632c29b404f6446" + }, + { + "version": "0.1.305", + "sha": "fad26166a466de2d3533523e4fad69f93af01bc2" + }, + { + "version": "0.1.304", + "sha": "47be4ae502b728222b219aeb1d630ad273690eb5" + }, + { + "version": "0.1.303", + "sha": "6827fa922fc688c2af43bde0baef2ce0bc34fad2" + }, + { + "version": "0.1.302", + "sha": "c31a7df4cae151daa6515f0e754f388c7764eebb" + }, + { + "version": "0.1.301", + "sha": "770252b55aef80f777820e37bd9944a875858ddf" + }, + { + "version": "0.1.300", + "sha": "c256f2985cadf1ce7617bbc86312b9a8e21e79b0" + }, + { + "version": "0.1.299", + "sha": "f1cddfb60117dada470af05f0b400c3e78984ee9" + }, + { + "version": "0.1.298", + "sha": "d2ff089b9554708dff7c473d3f25246920fadac0" + }, + { + "version": "0.1.297", + "sha": "12b37690ec95afef6c7fd4a6e575a13f4104bd52" + }, + { + "version": "0.1.296", + "sha": "a1472ff29fad05013819640b53bca42c214b455f" + }, + { + "version": "0.1.295", + "sha": "f076c3af9cbba8a23f6e8f9749e926ea963fd118" + }, + { + "version": "0.1.294", + "sha": "59eb12296a7fa4abdc9ec149426c72ee68a9e779" + }, + { + "version": "0.1.293", + "sha": "6ec18212948f368a266d3274bcf2f203535805bb" + }, + { + "version": "0.1.292", + "sha": "099e58b5dc902e7f463a8404d3aa864481547c27" + }, + { + "version": "0.1.291", + "sha": "e760d85427ff733726a591778d97e6fdbeae4dc6" + }, + { + "version": "0.1.290", + "sha": "98e7279632227aea8ce2281db5843264ccb6d423" + }, + { + "version": "0.1.289", + "sha": "30f223bcc4ef19aea5622468c2baf39d08e5b6c7" + }, + { + "version": "0.1.288", + "sha": "2cbeca8d1d865d3b2b0b0d1e6c04567991a394f8" + }, + { + "version": "0.1.287", + "sha": "b39902ef9060b955436d47ae9c705efe0cf81abf" + }, + { + "version": "0.1.286", + "sha": "68f03bb30e3bd1b2d2bf812583904691b6a85e7b" + }, + { + "version": "0.1.285", + "sha": "93c0f34aac02af0341dc7d9dfb35f669a21c6dd3" + }, + { + "version": "0.1.284", + "sha": "98ccb9366e4070b2ffa2d080186c2ce023e370a0" + }, + { + "version": "0.1.283", + "sha": "07a9caf22b61586bd790bd8a78c08be66a5a5e27" + }, + { + "version": "0.1.282", + "sha": "eea0a0ec3b929b91e95d1464310d6eb337c2de11" + }, + { + "version": "0.1.281", + "sha": "1d2fff8639613eb11514c6d94d805b2f71e86e6c" + }, + { + "version": "0.1.280", + "sha": "066dc45a2563e924db49c98327f129c034c8667b" + }, + { + "version": "0.1.279", + "sha": "ff257064c3bef2fef7baf837cc597eda72e6d11b" + }, + { + "version": "0.1.278", + "sha": "de867078c6da4a7aeac0a403b89cb8c9cf218e61" + }, + { + "version": "0.1.277", + "sha": "b15633d37c340f4ee971aca845aa0ebe5db4fb98" + }, + { + "version": "0.1.276", + "sha": "c938a4f4bf257be90ba8963328ec5368e92b2903" + }, + { + "version": "0.1.275", + "sha": "ba1989b04217b0b268e1068ab1ff40ac270029af" + }, + { + "version": "0.1.274", + "sha": "a98828dc4a5eb064066e7bfd31a9e513b2f56a34" + }, + { + "version": "0.1.273", + "sha": "e292b6f674bcefe893c6f295190c68ec5d20b5c1" + }, + { + "version": "0.1.272", + "sha": "30686dd288448a243bcfcd9e5e3742060ffef379" + }, + { + "version": "0.1.271", + "sha": "8ce78e28fabf99eb0093abb7d051c779fda71658" + }, + { + "version": "0.1.270", + "sha": "044fb37b2c7ae14153db35920fec4350963bd9ad" + }, + { + "version": "0.1.269", + "sha": "e34306b2dbbe58bde2de613be62291fe84c4cdcc" + }, + { + "version": "0.1.268", + "sha": "943b617b1f778a5a75db13eb2d9e50b8180f3569" + }, + { + "version": "0.1.266", + "sha": "99055a582eea241909089ec78ee6fadc45fdcdb6" + }, + { + "version": "0.1.267", + "sha": "90ab1c3a5ede520fecd2d71362c0699dd16e992b" + }, + { + "version": "0.1.265", + "sha": "0df0e3cb1e076e1a19bfd7da9eee38ba9ea29c47" + }, + { + "version": "0.1.264", + "sha": "f4bd1980bfeda7f4d5e9f5138d13e025726ca82e" + }, + { + "version": "0.1.263", + "sha": "8df19f9368193a83393f2422b7808fe7e0b00077" + }, + { + "version": "0.1.262", + "sha": "8d838d9cb40143b1400b95757aeb9bb0f03be2ab" + }, + { + "version": "0.1.261", + "sha": "5afd8c0140e3929ed2165057e3154c372cf6eea3" + }, + { + "version": "0.1.260", + "sha": "8febe12d7df42a0a945445d709a379c52de9d4c1" + }, + { + "version": "0.1.259", + "sha": "5eef13b6151a1f4b56861568cf38b3008df6c534" + }, + { + "version": "0.1.258", + "sha": "d0c6d57283f4a5bc16db2dfd97d2eb3c737aaad7" + }, + { + "version": "0.1.257", + "sha": "ee9d0787946c7cef2f7fe3b918c34cd6e6818aa9" + }, + { + "version": "0.1.256", + "sha": "a50cd734233e07fff941d6a3778f48f4d95c6de5" + }, + { + "version": "0.1.255", + "sha": "286f1fb375331658e86ebb84278e061a5d2954a1" + }, + { + "version": "0.1.254", + "sha": "3e4020c327c7226859da6cd7d9b5812ebfd5d519" + }, + { + "version": "0.1.253", + "sha": "56f8f4fd7ea659b897cc127ed7760429ef1fc6bf" + }, + { + "version": "0.1.252", + "sha": "7ab2eb1ccf4c3bc716ca6c103c163b9b8ae49cfe" + }, + { + "version": "0.1.251", + "sha": "323f9ae50230dc00608f32b9ad53e32f02973cc6" + }, + { + "version": "0.1.250", + "sha": "52c0186172319c08ab3d62bb4473707e0492fbc1" + }, + { + "version": "0.1.249", + "sha": "657fd251fbcc2d1e8d1724b182ac98ec608cfa07" + }, + { + "version": "0.1.248", + "sha": "468ae6491891607645ff9c90c1358eb37e288729" + }, + { + "version": "0.1.247", + "sha": "06c2db19fcd7a4bf3c7f0bbee0686ca730c4933e" + }, + { + "version": "0.1.246", + "sha": "70c181370db2339c9157c3f41c055e388e158e3e" + }, + { + "version": "0.1.245", + "sha": "8a68ed1819afb159e7d269f7b92819e539527794" + }, + { + "version": "0.1.244", + "sha": "9300865d23436a7426b535e1577ab2557b5dc57c" + }, + { + "version": "0.1.243", + "sha": "6400fd1bbedb1e9e7d58c3e60b8b9f69d8c903a4" + }, + { + "version": "0.1.242", + "sha": "e6472b0ec67684a6e0e09372d67699ac9454f0b3" + }, + { + "version": "0.1.241", + "sha": "a7d643635ad8de1fae3a1e65dd93edc436e17c28" + }, + { + "version": "0.1.240", + "sha": "afe9affc2aea5b84d936c257ad853a58770e4d2a" + }, + { + "version": "0.1.239", + "sha": "98294b9583c0c877f5dd31db61e5146ff548010b" + }, + { + "version": "0.1.238", + "sha": "4f0852a94dd36aca68b646f192b4dc2e6ba51ac1" + }, + { + "version": "0.1.237", + "sha": "c2ab658033ffaa2ae8001134528214fe14f25c07" + }, + { + "version": "0.1.236", + "sha": "d0310761b6a266722cbe772ab997727c69454183" + }, + { + "version": "0.1.235", + "sha": "780ab988c4676fab1816415fa35d15288343063a" + }, + { + "version": "0.1.234", + "sha": "c57c22c65e7e9f9a9a739b80597c65c34760e7b5" + }, + { + "version": "0.1.233", + "sha": "8a3aa800a3e0ecd59a4878557f3ec47ee99f891c" + }, + { + "version": "0.1.232", + "sha": "85b3596e0ef5c9fc54f35b691b92721bd80b3b25" + }, + { + "version": "0.1.231", + "sha": "6bdc17474c344fd8f343363515fa6ee67bd936c2" + }, + { + "version": "0.1.230", + "sha": "d5447280018614224e902f34595b57df35b258e7" + }, + { + "version": "0.1.229", + "sha": "4a70c2a848f687ed94dbd841a6ed10f3e5243219" + }, + { + "version": "0.1.228", + "sha": "d0fe257531b7412c60b238e44c4d25f6f82a5cb7" + }, + { + "version": "0.1.227", + "sha": "1d70059d328977fcae01589992d16f5d407dbe5f" + }, + { + "version": "0.1.226", + "sha": "ba852f3dc20119cf9a18aebde73d217acbe6f63d" + }, + { + "version": "0.1.225", + "sha": "a19cf819fc4284e722129c52701fbd3ae45b355d" + }, + { + "version": "0.1.224", + "sha": "465cae80bd00a70d054036e6bef68862a3ff8818" + }, + { + "version": "0.1.223", + "sha": "ed307f3c2014f5896c2580795402e32db1af5453" + }, + { + "version": "0.1.222", + "sha": "0d3a016a9e06657088a82c591f3ba60238cafee1" + }, + { + "version": "0.1.221", + "sha": "0088004cd6cec924092a39de63b95c0b570d7045" + }, + { + "version": "0.1.220", + "sha": "67743f22854d4bc378be7030ac30aa72f196e07b" + }, + { + "version": "0.1.219", + "sha": "f0bd6fb0d250aa713767638a973c3916c530008f" + }, + { + "version": "0.1.218", + "sha": "27828ff21e7c66846ba41ed50894814915fdf36f" + }, + { + "version": "0.1.217", + "sha": "18683ca63ca10dc8238d230eeb677608e911dd81" + }, + { + "version": "0.1.216", + "sha": "0252492e53b76c48c2deca86f5c497c055ba73ac" + }, + { + "version": "0.1.215", + "sha": "cf3f4662ccb88d6b6bf640749e628ec16017ccab" + }, + { + "version": "0.1.214", + "sha": "74fb04b2d1e50b1f227a19d0c0582394beff4f4f" + }, + { + "version": "0.1.213", + "sha": "402173263ecc2e18c2f216ead0b397b7c79f5d6e" + }, + { + "version": "0.1.212", + "sha": "72ea9726e7073d5c4cbb8b459cfef0cd1c48e38d" + }, + { + "version": "0.1.211", + "sha": "168fc2ecb7427140233825460c3a9897b4677ed3" + }, + { + "version": "0.1.210", + "sha": "f0f5571518bd644429da4fa3148219da2e0e1042" + }, + { + "version": "0.1.209", + "sha": "a0a7a266394557e338860eb8b272349ee44bfc4c" + }, + { + "version": "0.1.208", + "sha": "e0768f2caadb3359b680586b9355157f20987062" + }, + { + "version": "0.1.207", + "sha": "034c0b3a1c0b9fc5884750cc0d9929c0df4ac314" + }, + { + "version": "0.1.206", + "sha": "5defe238e0b992ab4d0037f63d04606d9e16cd2c" + }, + { + "version": "0.1.205", + "sha": "c27a0c14cc0a2a34f61b80e4a2d40994ba44ae2e" + }, + { + "version": "0.1.204", + "sha": "a42b101a7b50cadd70b9e369c32aaeb0311712ae" + }, + { + "version": "0.1.203", + "sha": "3284658eb1d5d64de8693f83732fb4ed3030293f" + }, + { + "version": "0.1.202", + "sha": "517d5f862df92230c494ae3bdeddd06021810a9e" + }, + { + "version": "0.1.201", + "sha": "f9c15a8f14353865258fd1c4bba19c9df5f621df" + }, + { + "version": "0.1.200", + "sha": "271f9f5f0992d03e409affe0f1fd91326fa33f5e" + }, + { + "version": "0.1.199", + "sha": "24d2546cbb99e2d5d1d8e126a1bf5d06af9ee457" + }, + { + "version": "0.1.198", + "sha": "691e4e0225b8b2b861be80a58a134158c81603ee" + }, + { + "version": "0.1.197", + "sha": "8d2ed8ebe61fcf5ef9a5e1b25923e6456c52c5c8" + }, + { + "version": "0.1.196", + "sha": "c14dd01f08ca1d7a5fccbe5ffdc0b69ed3463962" + }, + { + "version": "0.1.195", + "sha": "6ed06357f9a0fa7787796beb149d053407a6b3eb" + }, + { + "version": "0.1.194", + "sha": "5d42cba6c628fa32dc41caa05d3580e4d1924c45" + }, + { + "version": "0.1.193", + "sha": "a47f9323e060074d90eb1a3db53d826711e81ba5" + }, + { + "version": "0.1.192", + "sha": "4c79052a189ad2be3892498905774d77fc40a67e" + }, + { + "version": "0.1.191", + "sha": "18276d0839ce44b4fbf36675ced45884ab379cae" + }, + { + "version": "0.1.189", + "sha": "c40696145b43a47ea0cef6d83ae2fbe00bb3677f" + }, + { + "version": "0.1.190", + "sha": "cfad0034995de0fdd4a437de336502dc574fb0b3" + }, + { + "version": "0.1.188", + "sha": "47eb23cf6873e3c3487ef3b07cbfd547e43dd5f9" + }, + { + "version": "0.1.187", + "sha": "a06945516fe8de5a41c0bc7ae765627d536fb02d" + }, + { + "version": "0.1.186", + "sha": "e508abd958834d93515e86add2fa2e89a5b09e4c" + }, + { + "version": "0.1.185", + "sha": "c542a1333a869cfe1c8ef855c8e22640a9607419" + }, + { + "version": "0.1.184", + "sha": "1aecd9d6af0e2f7e8da6710f9bc73711ecb76e81" + }, + { + "version": "0.1.183", + "sha": "c2402947a96e19b26279c8f93168d7b67c87d3eb" + }, + { + "version": "0.1.182", + "sha": "958292d1ff2246b39c43bfe7d93e2b15e407bf6f" + }, + { + "version": "0.1.181", + "sha": "bc49b85b5627b5947b87d96df2089b444fd4df75" + }, + { + "version": "0.1.180", + "sha": "a6406209bd2a4a037ba5804bdaae8f78d255bbbb" + }, + { + "version": "0.1.179", + "sha": "b061a9686c636bb8d92419907f68fd3703baf678" + }, + { + "version": "0.1.178", + "sha": "2e39e495694a4d7f69273a4704ea1eb2ad9675fc" + }, + { + "version": "0.1.177", + "sha": "cb0090802b51dba5de2b72af8c9c348e18d80369" + }, + { + "version": "0.1.176", + "sha": "7d4ffdbfe5feb2d36f8ea5aa92009b57891086e2" + }, + { + "version": "0.1.175", + "sha": "84e8a0dfdcaf4fe42e650ebbb00b839e97f9b83c" + }, + { + "version": "0.1.174", + "sha": "faaf003707d17dd8550d3be5c98f4cf3c2420e82" + }, + { + "version": "0.1.173", + "sha": "4d6ecda3102e9f3d93d828a2ca8aa99d29669750" + }, + { + "version": "0.1.172", + "sha": "f2d6f3f64682680173ac8e697cb27e0f7bb73a24" + }, + { + "version": "0.1.171", + "sha": "37a8370f7be519f327334adb913173bd9d41abed" + }, + { + "version": "0.1.170", + "sha": "11c52ad81b425355205221666aae1b1561b71ef3" + }, + { + "version": "0.1.169", + "sha": "ee7688f24eb5a5b9f6dcd18afc6bb672fdaf0f6d" + }, + { + "version": "0.1.168", + "sha": "9d0fe33c130fe95ffffd105251d5f9d1c7491139" + }, + { + "version": "0.1.167", + "sha": "f82eb0735f4cae339c542ac52c953256d3a7126b" + }, + { + "version": "0.1.166", + "sha": "1faf5137ea53adee61e7c0aac4940a39ec832139" + }, + { + "version": "0.1.165", + "sha": "fd71ea349e81694c8a9b4892379a1d49f24dd273" + }, + { + "version": "0.1.164", + "sha": "906c282986c2fa3576616bd52fd1e4024f12b022" + }, + { + "version": "0.1.163", + "sha": "23cc6c0afe08de3110aae4beaf3bb0caaf5815da" + }, + { + "version": "0.1.162", + "sha": "1f482e4b49b24ca30150d66ce2400059310b4fbd" + }, + { + "version": "0.1.161", + "sha": "0ef79846e83bff0469b47a5dd20366e04498592c" + }, + { + "version": "0.1.160", + "sha": "c7dafe987e8267cf65bc9904099c74858d748116" + }, + { + "version": "0.1.159", + "sha": "f055fc174dc40d0dfbee35ed262144abe13c303c" + }, + { + "version": "0.1.158", + "sha": "b007acd4437d269778b2033dcf57e66e8f007490" + }, + { + "version": "0.1.157", + "sha": "7e4900b51b809b10f08efb91801d28cd6d1f07b0" + }, + { + "version": "0.1.156", + "sha": "1a04dfb4faf2e0ac4e76a63c89c9e32d384c3fa1" + }, + { + "version": "0.1.155", + "sha": "5afe151c7d9648e23b5c8c341af98f81629aaa46" + }, + { + "version": "0.1.154", + "sha": "faf8027890463be00fdfecaf3d9899f8d431edd5" + }, + { + "version": "0.1.153", + "sha": "3643e9bf82f01ce1b78ecc0a52b67a6ef2f77d12" + }, + { + "version": "0.1.152", + "sha": "ec737a9a65822b2a9d5bf893bce6a54d0fbe5c7b" + }, + { + "version": "0.1.151", + "sha": "96fffb5081142a843103637b54963f1e384abc01" + }, + { + "version": "0.1.150", + "sha": "962bed8f53236925b188f603547c3c3236878f8e" + }, + { + "version": "0.1.149", + "sha": "38d04488de00455b29e3d62d3d34de7271d05eaf" + }, + { + "version": "0.1.148", + "sha": "6fe98c9a30a68282598afa4a25961b2fff1a17ce" + }, + { + "version": "0.1.147", + "sha": "fe6e218d9cd5b600e90ed2986204337869225a29" + }, + { + "version": "0.1.146", + "sha": "1609a0c9be5d5192df5bfa9414473346d179ac61" + }, + { + "version": "0.1.145", + "sha": "d1dbb2b9454836ce9d382981d25eccfa0c6cc152" + }, + { + "version": "0.1.144", + "sha": "288e01a01aef1b3fe5c895b0fe9dc06b4b84d7f4" + }, + { + "version": "0.1.143", + "sha": "b76a433574b17c27607ca3146724652f73330264" + }, + { + "version": "0.1.142", + "sha": "def6022048e9bf3273d99d8a2eddfc1178048421" + }, + { + "version": "0.1.141", + "sha": "e7ba2057aca11a5b56bff96156e1af1360a8b02a" + }, + { + "version": "0.1.140", + "sha": "cef25d92a62ace37c5ecdeed07ba270dbe50c6cc" + }, + { + "version": "0.1.139", + "sha": "f722ea87271a9d8d5bec465ee07f75bba4861d54" + }, + { + "version": "0.1.138", + "sha": "e47e6d825dd21d883ae529e705dbc6306c411d7a" + }, + { + "version": "0.1.137", + "sha": "7a2188a0ee7cd17c93142f25450128781c37a856" + }, + { + "version": "0.1.136", + "sha": "ea4cffbcaa54c0055ddcaeb812cb93a8ededbce3" + }, + { + "version": "0.1.135", + "sha": "c72b71d78afd1d72cefdc6ced301001172ccc968" + }, + { + "version": "0.1.134", + "sha": "ca51cbe2029cec6ddc41d978c378d2d67c2564b2" + }, + { + "version": "0.1.133", + "sha": "6fea763c9baf17b6f1e445b0f8b6fe525a3e928f" + }, + { + "version": "0.1.132", + "sha": "ac3b39510247b9f26458a578d47cbad2b5846aa3" + }, + { + "version": "0.1.131", + "sha": "8a68ea2da240134f5ac484d74412468048261473" + }, + { + "version": "0.1.130", + "sha": "a2d051f6b3e7e0895eb4aaafdb941093536d82f5" + }, + { + "version": "0.1.129", + "sha": "0b9dbe12cfbb30584b2dd3dc1a726822cd5c7f4e" + }, + { + "version": "0.1.128", + "sha": "6ca8481e8e8c22f401d3ef1cbea9e9b9dbe2de6f" + }, + { + "version": "0.1.127", + "sha": "8da4666e76f8b46cad29e41d2fdd0b98663c53bc" + }, + { + "version": "0.1.126", + "sha": "281699a43104af6ce5344b17375ac7102f3e563d" + }, + { + "version": "0.1.125", + "sha": "444b8202108adc4309d6cc77da77eefe089fad71" + }, + { + "version": "0.1.124", + "sha": "67a48faf23996b7cf55d2ed589e4235f9b4d4754" + }, + { + "version": "0.1.123", + "sha": "7e3075f0fc77c51821925d7c7194c6c759678b87" + }, + { + "version": "0.1.122", + "sha": "2038462dbbd09b4bee00ea8d647c2bb0e94f538d" + }, + { + "version": "0.1.121", + "sha": "7b8cc0384b9160d488be6ed308b2007d9d6ef91f" + }, + { + "version": "0.1.120", + "sha": "f91359f881077d1fad17fca021973c6beb54553f" + }, + { + "version": "0.1.119", + "sha": "12bb839d8a2cc138d3e4034d4dae559a52c3d094" + }, + { + "version": "0.1.118", + "sha": "3ad48ff32cf301b70555e93409d8593b3b571be0" + }, + { + "version": "0.1.117", + "sha": "e313ae12f29bd5579f779122cf6a26a87a98a87d" + }, + { + "version": "0.1.116", + "sha": "e3db157ba8d78f6046114055a24f7812e224e576" + }, + { + "version": "0.1.115", + "sha": "7e1610da44387c1cd86af2605311997e8249c47a" + }, + { + "version": "0.1.106", + "sha": "9f990158b97c0f6d0ec010b01a80f540042d5945" + }, + { + "version": "0.1.95", + "sha": "74aef1e373513ec605750313b7002528575bf50e" + }, + { + "version": "0.1.94", + "sha": "1b09f7d35bcb7a4b2a79417996933b1b45a31c95" + }, + { + "version": "0.1.93", + "sha": "d8d143eca004b83cbd9226f088ddf8ad79f2317f" + }, + { + "version": "0.1.92", + "sha": "52f411783581f3aa586ff86838ca99ba69b1b251" + }, + { + "version": "0.1.91", + "sha": "7448d130ec71cbfd4f31b9a2eff4bc8ef7b57f2e" + }, + { + "version": "0.1.90", + "sha": "f0b12412a1fa6cd7251692cc485369c3dd789f1c" + }, + { + "version": "0.1.89", + "sha": "5b6e154459d510a26e8434ad521f5009b803a0a1" + }, + { + "version": "0.1.88", + "sha": "8930ef29cdfd091dfb0897c195799fba002e12bd" + }, + { + "version": "0.1.87", + "sha": "a55d7f454e74f3a123b62180adad90a7ab16868b" + }, + { + "version": "0.1.86", + "sha": "b55292bba16666fd2a7b20c1f06fd3a440781bc0" + }, + { + "version": "0.1.85", + "sha": "02ee5da231d8f761a756db06aad4ab4b3a225767" + }, + { + "version": "0.1.84", + "sha": "e744114ff438cbbf15d98dd46a5e74f900d0b1be" + }, + { + "version": "0.1.83", + "sha": "c8f05ee46f61ee2ec6a8f20a31fea5783a61b283" + }, + { + "version": "0.1.82", + "sha": "1e8d8e3cdf30d94fad4b99ce1e5ede3da5d402fd" + }, + { + "version": "0.1.81", + "sha": "842bfccc1b92613212589b66db39a24ad29975ed" + }, + { + "version": "0.1.80", + "sha": "740f0e69baf33f64b9dec7b5689f56cd3159baa6" + }, + { + "version": "0.1.79", + "sha": "020cae1228d2c5a30808baadf42444fdd91a3236" + }, + { + "version": "0.1.78", + "sha": "017cbcb851901c1ad0a7571aaea211fafae0ace7" + }, + { + "version": "0.1.77", + "sha": "b3992bfa0f5c61cdb102393d8fba12b9cae58308" + }, + { + "version": "0.1.76", + "sha": "2902ba0af2dbb01bbab49dee5662f23833d74d56" + }, + { + "version": "0.1.75", + "sha": "555bc6ff5614a672d82ff5c103f92da04b86bb93" + }, + { + "version": "0.1.74", + "sha": "1dda055e4c5ea2b0556141ee5efac7de2ba2e526" + }, + { + "version": "0.1.73", + "sha": "f87d3fad9be11f1164137d1e28f2599dccab43fd" + }, + { + "version": "0.1.72", + "sha": "23b19350cce09a18a52ccfa9d0e66e9b2c2539a6" + }, + { + "version": "0.1.71", + "sha": "1a9d1fff8391e531b0af6b6b32f0ae0ee86e057a" + }, + { + "version": "0.1.70", + "sha": "822fb652ffaea9088e31f87ad1abf5a8b4fdc3ff" + }, + { + "version": "0.1.69", + "sha": "600e9a2dc2464d0013f40375d71ab3937ce4e401" + }, + { + "version": "0.1.68", + "sha": "eed83039ab08c7ed88a6bf2c01c3d1f0ad299bdb" + }, + { + "version": "0.1.67", + "sha": "43b1e423fec01a3534dac4dfba9b6152d4bab36c" + }, + { + "version": "0.1.66", + "sha": "5aa32492de54c9ac32841e8fef96489697611d10" + }, + { + "version": "0.1.65", + "sha": "68162895528ef3a1f42be18693d32915fc3abfde" + }, + { + "version": "0.1.64", + "sha": "1d33ff697632c8ab81ce2ae305ed200ef1fd2da6" + }, + { + "version": "0.1.35", + "sha": "330fd12b3f30376c3aee757ba2a9f4ba3f8bcf27" + }, + { + "version": "0.1.28", + "sha": "f33730ed7437536c3d1e01cc2f20231af076b531" + } +] \ No newline at end of file