- 矩阵:新增 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 部署方案同步成本轮真实结果。
This commit is contained in:
@@ -52,7 +52,8 @@ LIBRARY_ARCHIVE_ROOT=/var/lib/knowledge-governance/library
|
||||
「目标维护未接线,拒绝在挡不住入口/后台写入的情况下写目标」(`cmd/worker/library_handlers.go`)——**不会**退化成"库级锁假装维护"。
|
||||
- 缺 `LIBRARY_PRODUCTION_DATABASE_URL` 时 overview/差异按"未接入"显示,不显示 0 或被当成"无变化"。
|
||||
- `/api/v1/library/maintenance` 在实例未配置时返回 `instance` 缺省、`held=false`;页面显示「未接入 + 维护类操作 fail closed」。
|
||||
- `LIBRARY_AGENT_SOCKET` 指向的 agent 目前**没有运行**:`POST /api/v1/library/captures` 会 503 并写明"未接线",不会假装已封存。
|
||||
- `LIBRARY_AGENT_SOCKET` 指向的 agent 必须在部署时一起起来(`scripts/library-agent-install.sh` + 0600 `agent.env`);
|
||||
端点/闸门/队列声明与 worker 同源(`LIBRARY_TARGET_*`)。缺任何一项时封存会在开窗口前被拒并点名缺什么。
|
||||
|
||||
新增配置只在需要真正开维护窗口时才加(本机验收实例的取值可作模板,见
|
||||
`evidence-maintenance/harness/candidate-maintenance.env`:`LIBRARY_TARGET_INSTANCE/PROJECT/WRITERS/NON_WRITERS/DOCKER_SOCKET/INGRESS_*`、`LIBRARY_TARGET_REDIS_*`)。
|
||||
@@ -85,6 +86,8 @@ LIBRARY_ARCHIVE_ROOT=/var/lib/knowledge-governance/library
|
||||
## 7. 已知风险与前置条件(必须先解决或明确接受)
|
||||
|
||||
1. **`knowledge-governance-p1-release-worker-1` 处于重启循环**(日志每分钟 `connect release target database`,`RELEASE_WORKER_ENABLED=false`)。这是正式栈既有状态,不是本轮引入;部署前需要决定是修配置还是先停掉该服务,否则会持续刷日志。
|
||||
2. **封存能力未接线**(见矩阵"缺代码"):正式上线后页面能如实显示"未接入",但"封存测试版本"按钮仍不可用。如果验收要求现场封存,需要先部署 `libraryagent`(`cmd/libraryagent`,0600 unix socket)并把 `LIBRARY_AGENT_SOCKET` 指到它。
|
||||
2. **封存能力已在本机专用实例验证**(2026-10-29:页面入口 → agent → 实例级窗口 → 同步 → 恢复)。
|
||||
正式部署仍需按同一形状接线:`libraryagent`(0600 socket)+ `agent.env`(`LIBRARY_TARGET_*`)+ `LIBRARY_AGENT_SOCKET`;
|
||||
这套接线在正式栈上尚未执行。
|
||||
3. **入口排空探针未在最终构建复现**(矩阵"已实现·未跑"):正式部署前若要拿这条证据,需要在生产机重复探针,并接受"闸门关得比客户端快"导致上传注入失败的可能。
|
||||
4. 部署本身就是生产变更:需要用户给的窗口与授权,本文不构成执行许可。
|
||||
@@ -44,6 +44,11 @@ A06(内容、问答、引用图片效果、性能报告)已由用户取消
|
||||
| **S03** | 实例级维护窗口:关入口→排空→静默证明→停写者→写入→校验→开入口 | `internal/libraryfreeze` + `librarymaintenance` + 迁移 070–072 | 前轮 7 场景真跑全绿(normal/kill/applyfail/partial/crossscope/recover_refuse/recover_ok),本轮 sync/drain 场景继续复用;`quiesce` 证据、`post_stop_*`、journal、gate drain.json 均可回看 |
|
||||
| **V03-a** | 撤销一份共享贡献后,保留另一份贡献、清除撤销方独有正文/引用/实体边、目录正确 | 由 WeKnora 源侧执行(`cleanupWikiOnKnowledgeDelete` + `EnqueueWikiRetract`),治理侧只比对/搬运 | **本轮真实验收**:kgmaint 隔离实例、原版 API 顺序入库两篇文档 → `entity/fa-bu-zha-men` 同页双来源(2097 字节)→ 真实 `DELETE /api/v1/knowledge/{id}` → 页面保留、来源只剩保留文档、A 独有正文(ZEBRA-A-7731/9910)清除、B 贡献仍在、引用 2→1 且只剩 B 的分块、目录不变、A 独有页面/摘要整页删除、索引页不再列出、实体边无死链。13/13 检查通过。证据:`kg-candidate-e2e/evidence-wiki-final/s8-final-checks-r1.json` + `06-wiki-*-r1.json` + `s3/s6-db-*.json` |
|
||||
| **V03-b** | 治理侧能把上述语义变化说清楚(不是页面级"无变化") | `internal/librarydiff/wiki.go` `CompareWiki`;`GET /library/overview` 的 `diff.wiki` | 逐页比对正文/来源/引用/出入边/目录,输出 `sources_removed`、`citations_before→after`、`changed`;单测 `wiki_test.go`、视图单测 `library_wiki_diff_test.go`;页面逐行展示(`wikiContributionLines` + 测试) |
|
||||
| **S06** | 页面/API 发起封存 → 主机 agent → 实例级窗口封存(真实冻结) | `POST /library/captures` + `cmd/libraryagent`(0600 socket、固定命令)+ `libraryctl capture`(实例级:闸门/后台静默/指纹) | **本轮真跑**:`POST /api/v1/library/captures`(job `lib-internal-20261029T0614`)202 → worker → agent → CLI → 关闸门排空 → 等后台静默 → 停 `kgmaint-app`(exit=1 但入口已排空,`drain_verified=true`)→ 导出 → 恢复写入者与入口;作业 `libseal-…` succeeded,快照 `1196c518…`。证据 `kg-capture-loop/evidence/04-inflight-capture.json`、journal `freeze-kgmaint/lib-internal-20261029T0614.json` |
|
||||
| **S06-a** | 排空证明取自真实在途请求(不靠 202 后抢时序) | 闸门 `drain.json` + 客户端真实上传握手 | **本轮真跑**:先发一半 multipart 正文 → 闸门日志 `conn:open active=1` → 再发起封存 → 入口关闭(拒绝新连接)→ 补完正文,应用 200 并完成解析/嵌入 → `drain.json phase=drained active_at_close=1 served=126 drain_ms=302`;窗口 `quiesce.samples=18 stable_ms=1617` 且停写者后指纹不变。同一次窗口里派生任务真的跑完(探针文档 `de475a32…` completed / 10 分块) |
|
||||
| **V03-c** | 带"撤销后共享页"的整库封存 → 独立目标同步 → 恢复,逐项真实读回 | `libraryctl` 封存 + `ProductionLibraryTarget` + `libraryrelease` | **本轮真跑**:源 kgmaint 撤销后 KB `e64e82b5`(9 Wiki 页、1 文档、2 分块、1 资源)→ 快照 `1196c518…` 与源逐页比对全部一致(正文 md5/来源/引用/目录/18 条边)→ 同步到**独立实例** kgseal(自己的 PG/索引/桶,非同一份存储)`libsync-internal-1793226278273693890` succeeded+verify succeeded → 目标逐页/索引/资源字节一致(`07-target-compare.json`,`ok=true`,目标桶对象 sha256 与源一致)→ 人为偏离(删整库 Wiki 页)→ `librestore-internal-1793226571284097366-retry` succeeded → 目标回到与快照逐项一致(同一份 `07-target-compare.json` 摘要,`ok=true`) |
|
||||
| **S06-b** | 主机侧封存/恢复必须与 worker 讲同一份维护声明 | `internal/librarymaintenance/env.go`(`LIBRARY_TARGET_*` 唯一解析处) | 缺项时 `capture` 在开窗口前拒绝并点名;agent 启动打印维护目标;单测 5 项(逐项缺失/空白/口令空格/队列库号) |
|
||||
| **Neo4j 启用场景** | 启用图谱的库必须被真实拒绝 | `v071/library_export.go` 读 `indexing_strategy.graph_enabled` | **本轮真跑**:在 kgmaint 建 `graph_enabled=true` 的真实 KB `8de41ca7…`,用真实 CLI 预检 → 退出码 1、`LIBRARYCTL_FAILED: Neo4j graph snapshot is not supported`(`evidence/10-neo4j-refuse.txt`)。不是 skip |
|
||||
| **U04** | 维护级别/进度/恢复错误真实呈现 | `MaintenanceCard`(`web/src/ReleaseTargets.tsx`)、`/api/v1/library/maintenance` | 显示实例级/库级、持有 scope+作业、阶段、入口开/关、服务、开始/释放、进度(阶段名中文化)、恢复错误与处理记录;仅"窗口仍持有且作业失败"时给"按预备份恢复";无记录时"未接入 + fail closed"。组件测试 6 项 |
|
||||
|
||||
## 二、缺代码
|
||||
@@ -51,9 +56,7 @@ A06(内容、问答、引用图片效果、性能报告)已由用户取消
|
||||
| ID | 缺口 | 现状与影响 |
|
||||
| --- | --- | --- |
|
||||
| R03 | 存快/恢复快对比 | 没有测速实现,也未测。不写没有测量的数字 |
|
||||
| 死信解除契约 | 死信只能靠人工清理,没有"解除/豁免"契约 | WeKnora `task_dead_letters` 无解决标记;导出/封存按"存在即阻断"(`library_source_settlement.go`)。本轮真实验证:早期错误模型配置产生的 2 条 wiki 死信直接阻断整实例结算,清理方式只能是删除行(记录:`deadletters-before-delete.json`,sha256 `dd4d8f08…`)。要变成可运维契约,需要上游给出解决标记或治理侧设计"带证据的解除"——**未实现** |
|
||||
| 封存入口接线(本机) | `POST /library/captures` 需要 `LIBRARY_AGENT_SOCKET` 指向真实 agent | 候选/正式 api 都配置了 socket 路径,但本机没有运行 `libraryagent`,接口会 503 并明示"未接线"(不是静默失败) |
|
||||
| CLI 实例级封存 | `libraryctl capture` 只建库级窗口,不关入口 | 真跑结果:WeKnora 收到 SIGTERM 固定 `exit=1`,没有入口排空证明时被拒(`未优雅退出(exit=1)`)。要在"不停共享栈"的前提下封存真实撤销后的库,需要把实例级 + 闸门接进 CLI(或部署 agent)——**未实现** |
|
||||
| 死信解除契约 | 死信只能靠人工清理,没有"解除/豁免"契约 | WeKnora `task_dead_letters` 无解决标记;导出/封存按"存在即阻断"(`library_source_settlement.go`)。本轮真实验证:早期错误模型配置产生的 2 条 wiki 死信直接阻断整实例结算,清理方式只能是删除行(记录:`deadletters-before-delete.json`,sha256 `dd4d8f08…`)。要变成可运维契约,需要上游给出解决标记或治理侧设计"带证据的解除"——**未实现**。本轮未再新增死信,没有用它绕过任何一项;仍需的最小外部契约见第五节第 5 条 |
|
||||
|
||||
## 三、已实现·未跑
|
||||
|
||||
@@ -62,9 +65,7 @@ A06(内容、问答、引用图片效果、性能报告)已由用户取消
|
||||
| V05 | 导出中源变更则拒绝或隔离 | 预备份导出前后行数静默检查已有(本机验证会拒绝);真实并发写入的隔离演练未做 |
|
||||
| B02 | 资源在测试侧删除后仍可恢复 | 归档内容寻址已实现;"删测试侧对象再从归档恢复"实测未做 |
|
||||
| S03-b | 死信/孤儿任务阻断的负向演练(本轮只验证了真实死信阻断) | 真实阻断已发生并记录;系统化的负向矩阵未跑 |
|
||||
| **V03-c** | **带"撤销后共享页"的整库封存 → 同步 → 恢复** | 上游语义已验证、治理差异已实现;但这台机器上 CLI 封存止步于上面的 exit=1 缺口,API 封存未接线,所以"封存一份含撤销结果的 Wiki 版本,再同步/恢复并逐行比对"**没有产出真实证据**。旧证据 `drain-task-new-c`(03:39)属于 0ef7984 之前的构建,不能拿来证明当前 SHA |
|
||||
| **Neo4j 启用场景** | 代码在 `library_export.go` 检查 `graph_enabled`,启用即 `Neo4j graph snapshot is not supported` 拒绝 | 本轮没有真跑"启用 Neo4j 的库被拒":kgmaint 实例 `NEO4J_ENABLE=false`、测试库 `graph_enabled=false`。代码路径在,真实执行未做 |
|
||||
| **排空竞态探针(最终构建)** | 关入口→排空期间产生的真实任务必须等到队列归零才停写者 | 本轮在最终构建(1faf513)上重跑 6 次(`drain-task-final-1faf513{,-r2,-r3,-r4,-r5,-r6}.json`),上传请求全部 `Broken pipe`、队列深度始终 0:闸门在 202 之后 <0.2s 就关监听,客户端来不及把请求送进去。**未复现**,不记为通过;旧 `drain-task-new-c` 的时间线仍是旧构建证据 |
|
||||
| 排空时序(历史结论) | 旧做法"202 之后抢时序上传"在 1faf513 上重跑 6 次全部 `Broken pipe`(`drain-task-final-1faf513*.json` 保留,不记通过) | 已被 S06-a 的确定性做法取代:先证明请求已被闸门接收,再启动维护。旧证据保留,不覆盖 |
|
||||
| U05 | 正式 8088 页面验收 | 正式栈仍是 0.1.114,本轮未替换;维护面板/逐页 Wiki 差异只在测试与候选构建里验证过 |
|
||||
|
||||
## 四、需授权 / 需环境
|
||||
@@ -82,5 +83,6 @@ A06(内容、问答、引用图片效果、性能报告)已由用户取消
|
||||
|
||||
1. **Wiki 撤销语义是上游行为,必须用上游 API 验**:顺序入库才产生"两篇共贡献一页"(并行入库会各自抽到不同 slug 生成两页,这是上游已知行为)。撤销后页面保留、贡献按文档摘除,12 项检查全部通过。
|
||||
2. **治理侧不能只报页面级差异**:撤销共享贡献时页面仍是"更新",页面级计数会显示成"无变化"。已改为逐页语义比对,能指出"来源 -1、引用 2→1、正文变了、出边少了一条"。
|
||||
3. **封存链路是本机当前的真缺口**:CLI 封存被 `exit=1` + 无入口排空证明挡住,API 封存没有 agent。这不是"Wiki 语义没验",而是"带撤销结果的版本没法在本机封出来"。正式部署必须先接线 agent(见 `docs/formal-8088-deployment-plan.md`)。
|
||||
4. **死信不是可忽略的告警**:一条失败的 wiki op 就足以让整实例结不了算。要么上游给解决标记,要么治理侧设计带证据的解除,当前两者都没有。
|
||||
3. **封存链路本轮真的打通了,而且不是放松校验**:CLI 改为实例级窗口(闸门 + 后台静默 + 目标指纹),"写者 exit=1"由闸门的 `drained/active_at_close` 证据解释,而不是被忽略;排空证明改成"先在途、再关入口"的确定性顺序,不再靠 202 之后抢时序。
|
||||
4. **接线后暴露的四个真缺陷(都已修,都有用例)**:`jobs.id` 列宽 36 让封存作业根本投不进去;v0.7.1 的 app 写索引不带 `tenant_id` 导致导出 0 条;全新实例的空索引被当成"索引被改坏";恢复作业 id 带 `.` 被维护窗口拒。另有资源定位符仍写来源桶——同步过去的目标取不到图/附件。
|
||||
5. **死信解除仍缺产品路径,最小外部契约如下(未实现,不冒充)**:①保留原始死信行与错误证据,不做删除;②解除必须在治理侧留下审计记录(谁、何时、依据哪条证据、绑定哪个实例/库/任务指纹);③只有在"重放该任务并成功、且源侧结清"之后才允许解除,解除记录挂在精确项上(不允许按实例或通配解除);④未解除的死信继续阻断结算与封存。本轮没有新增死信,也没有用它绕过任何门槛。
|
||||
@@ -107,6 +107,19 @@ LIBRARY_TARGET_REDIS_ADDR/_PASSWORD/_DB 后台队列(asynq)地址
|
||||
|
||||
两次封存都记录了 `stop_verified=true`、`restored=true`,`WKNFull-app` 恢复后 healthy;来源未做任何写操作。
|
||||
|
||||
## 2026-10-29 实测:页面入口 → agent → 实例级窗口(真实撤销后的库)
|
||||
|
||||
- 请求:`POST /api/v1/library/captures {scope:internal, job:lib-internal-20261029T0614}` → 202,
|
||||
worker 投递 agent,CLI 以**实例级窗口**执行。
|
||||
- 排空证据(确定性顺序,不抢时序):先发一半 multipart 正文 → 闸门日志 `conn:open active=1`
|
||||
→ 再发起封存 → 入口拒绝新连接 → 补完正文,应用返回 200 并完成解析/嵌入 →
|
||||
`drain.json`:`phase=drained active_at_close=1 served=126 drain_ms=302`。
|
||||
- 冻结:`kgmaint-app` 收到 SIGTERM 后 `exit=1`(上游行为),因入口已排空,`drain_verified=true`;
|
||||
后台静默 `quiesce.samples=18 stable_ms=1617`,停写者后指纹不变;随后恢复写入者与入口。
|
||||
- 产物:快照 `1196c518…`(KB `e64e82b5…`,tenant 10001):1 文档 / 2 分块 / 9 Wiki 页 / 1 资源,
|
||||
与源库逐页比对(正文 md5、来源、引用、目录、18 条边)全部一致。
|
||||
- 同一份快照随后同步到**独立实例**(自己的 PG/索引/桶)并成功恢复,逐项读回一致。
|
||||
|
||||
## 它证明了什么 / 没有证明什么
|
||||
|
||||
已证明(A04 数据完整性口径):这一份整库内容在**写入者停住**之后被完整导出,PostgreSQL 与 Elasticsearch 双向覆盖、生成问题、资源与摘要逐项核验通过,产物可寻址、可复读、可校验。这正是封版所需的完整性口径。
|
||||
|
||||
+15
-1
@@ -68,7 +68,21 @@ run164 因 qawemail 依赖外网 DNS 失败,已修,不涂改。
|
||||
| 0.1.166 | 1faf5137 | 用完整容器 ID 判定写入者归属(**验收代码 SHA**) | 207 | **success** | 否 |
|
||||
| 0.1.167 | f82eb073 | 矩阵四段重排 + 8088 待验收部署方案 | 208 | **success** | 否 |
|
||||
|
||||
当前候选 = 0.1.166(1faf513);其后的 f82eb07 只改文档,未改代码,验收证据的 code_sha 仍是 **1faf513**。
|
||||
| 0.1.169 | ee7688f2 | 矩阵改正 Wiki 检查计数(上轮交付写作 0.1.168,按 `git rev-list --count v0.1.0..HEAD` 实为 0.1.169,此处以台账为准) | 210 | **success** | 否 |
|
||||
| 0.1.170 | 11c52ad8 | 封存窗口接上实例级闸门/后台静默(**本轮第一个代码提交**) | 211 | failure(密钥扫描把"环境变量名字面量"误判为密钥;该提交之后才在下一提交改名修复) | 否 |
|
||||
| 0.1.171 | 37a8370f | 索引范围过滤接受未写 `tenant_id` 的文档 | 212 | **success** | 否 |
|
||||
| 0.1.172 | f2d6f3f6 | 封存队列作业 id 按 `jobs.id` 列宽拼短 | 213 | **success** | 否 |
|
||||
| 0.1.173 | 4d6ecda3 | 源对象存储端点可按宿主机地址覆盖 | 214 | **success** | 否 |
|
||||
| 0.1.174 | faaf0037 | agent 固定命令形态纳入对象存储端点 | 215 | failure(漏跑 gofmt,下一提交修) | 否 |
|
||||
| 0.1.175 | 84e8a0df | gofmt | 216 | **success** | 否 |
|
||||
| 0.1.176 | 7d4ffdbf | 全新目标实例的空索引按空基线 | 217 | **success** | 否 |
|
||||
| 0.1.177 | cb009080 | 同步资源行定位符换成目标桶 | 218 | completed/cancelled(被后续提交取消,不计) | 否 |
|
||||
| 0.1.178 | 2e39e495 | 移出一次性核对小工具 | 219 | **success** | 否 |
|
||||
| 0.1.179 | b061a968 | 资源换桶变换在校验时同用(**封存闭环验收代码 SHA**) | 220 | **success** | 否 |
|
||||
| 0.1.180 | a6406209 | 恢复作业 id 改成维护窗口可接受的形状 | 221 | **success** | 否 |
|
||||
|
||||
当前候选 = 0.1.179(b061a968)跑完封存/同步/恢复的真实闭环;0.1.180(a6406209)是恢复作业 id 的修复,
|
||||
恢复真跑证据在 0.1.180 上取得(见交付报告)。
|
||||
本轮真实验收证据:`/home/zheromiku/.hermes/handoffs/kg-candidate-e2e/evidence-wiki-final/`,
|
||||
交付报告:`/home/zheromiku/.hermes/handoffs/release-wiki-final-acceptance-result.md`。
|
||||
正式 8088 仍是 0.1.114(dc9e827),未部署;部署方案见 `docs/formal-8088-deployment-plan.md`。
|
||||
|
||||
Reference in New Issue
Block a user