Files
knowledge-governance/docs/USER_FLOWS_ACCEPTANCE.md
T

366 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户操作流程与验收清单
按流程逐项验收用。每条给出:**入口 → 用户步骤 → 期望 → 验收证据 → 状态**。
状态标记:**✅ 已实测**(附证据)|**⏳ 待验收**|**⚠️ 已知限制**。
深度细节见各自专题文档(`P3…`/`P5…`/`P6…`/`P8…`/`PRODUCTION_BACKUP_RECOVERY.md`/`WIKI_CO_TRANSPORT…`)。
统一入口:**http://127.0.0.1:8088/**(测试栈;右侧面板可直接操作)。角色:管理员 / 文档工程师 / 只读。
---
## A. 内容进入
### A1 取件(钉钉知识库 / PP 清单)
- 入口:**来源与目录 → 内容来源**(勾选条目;钉钉**扫码会话失效时先刷新会话**——注意:走 dws 取件后钉钉这条不再依赖浏览器会话)
- 步骤:选来源 → 展开目录 → 勾选 → 取件(后台入队)
- 期望:解析监控出现"处理中";失败进**死信**,可重排
- 证据:`解析监控 → 死信` 队列;重排轮次键 `artifact-fetch:<version>:r<n>`
- 状态:✅(死信重排修复 `cddc4c9`)
- **2026-10-10 端到端实测(界面操作)**:勾选 `App账号注册.adoc` → 标记"对内" → 取件
- 首次取件**终态失败** `artifact_fetch_rejected: credentials`(1/8 次即终态)——**已定位并修复**:
`artifactfetch` 的 runner 给子进程的是**白名单环境**(防凭据外泄),但没透传
`ARTIFACT_DINGTALK_FETCHER/DWS_BIN/DWS_CONFIG_DIR/DWS_HOME/DWS_DISABLE_KEYCHAIN`,
dws 找不到配置目录与文件型 keychain → 报"未登录" → 被映射成 `credentials` 终态
(且按设计不回落老路径)→ **钉钉取件在真实链路上整体失败**。脚本单跑正常,因为绕过了白名单
(提交 `d29e04e`;同时失败时把 stderr 尾部脱敏后带进错误,否则只能看到裸 code)
- **重排(重新入队)后**:`artifact.fetch` 1 次完成 → 产物 `sn-753f….docx` **4799 B**、
`file` 认作 Microsoft Word 2007+、zip 12 项;`content_objects` 记 `validated_format=docx`、
`content_state=verified`、`sha256=5d8f38d0…` ✓
- 教训:**dws 取件的端到端证据必须在"平台真实调用路径"里取**(脚本直接跑会漏掉环境白名单这类差异)
- **第二遍(2026-10-10,另一条路由)**:`技术文档/人形适配大寰电爪相关资料/DH-AG95夹爪适配-指令协议.md`(3513 B,**drive 直下**,不走 adoc/sheet)
- 路由回执:"策略已保存 1 份;已记入意图 1 份;排队从源头取件 1 份;列入待入库 1 份"
- `artifact.fetch` 一次成功;spool `sn-22da9a36….md` 3513 B、UTF-8、正文以 `# DH-AG95夹爪适配-指令协议` 开头 ✓
- **易误判点**:`content_objects` 按 **sha256 内容级去重**,命中已有行时只更新 `updated_at`/**不更新 `created_at`**——
本次复用了 2026-09-14 创建的 `co-e90af1d6…`(sha `55fae4b4…`、3513 B、`md`)。核对时必须走
`source_item_versions.content_object_id → content_objects`,不要按 `created_at` 过滤列表(我一开始就这么看漏了)
#### A1 补充:钉钉取件改走 dws(`f4e1ecc` / `6496ee3` / `9155bd3`,2026-10-09)
- 开关:`ARTIFACT_DINGTALK_FETCHER=dws|auto|legacy`,线上为 **auto**(先 dws;**仅可重试错误**回落老路径)。
- 路由:`adoc`→doc export docx;`axls`→sheet 读取转 xlsx;其余→drive 直下;`dlink/hlink`→missing;`amind/appt`→unsupported(详见 `docs/DINGTALK_DWS_FETCHER.md`)。
- 平台侧判定全部保留:后缀路由、PPT 必须 PDF 双胞胎、magic、大小上限、sha256、spool 原子写、空壳检查 → dws 只负责"取字节"。
- 本人复核(2026-10-09,在**已部署 worker** 内):
- 接线:`dws` v1.0.63(`Edition: open`)在 `/usr/local/bin/dws`;`DWS_HOME=/data/home`、`DWS_CONFIG_DIR=/data/dws`、`DWS_DISABLE_KEYCHAIN=1`;宿主两份 state 已按文档挂载 ✓
- 鉴权(**必须带 `HOME=DWS_HOME`**,直接 `docker exec dws` 会误报"未登录"):`authenticated:true`、`refresh_token_valid:true`、`refresh_expires_at 2026-11-08`、账号 姚章浩 / 深圳市越疆科技股份有限公司 ✓
- 真实调用:`dws wiki +space-list --limit 1` → `ok:true`,返回"公司级技术支持知识库" ✓
- 回归测试:`artifact_fetch_test.py` 18 项、`test_dws_error_map.py` 19 断言全绿 ✓
- 运维依赖(验收时作为背景知识):
- **refresh token 30 天轮换**:到期前 7 天按 runbook `dws auth login --device` 续期(本人浏览器授权);出现 `code=credentials` **立即停批**,不要重试风暴,授权后 `--retry-failed` 续跑。
- 在线文档并发建议 **≤4**(当前 `ingestion_concurrency=2` ✓ 满足);8 路混合时巨型文档曾现 `export_failed`(已判可重试)。
- 回退开关:`ARTIFACT_DINGTALK_FETCHER=legacy` 可完全绕过 dws。
- CI:Gitea Run **#6/#7/#8 全 success**(#8 = 含本清单的 `b1b4ca5`)✓
### A2 标注与目录范围(路由到哪个库)
- 入口:**来源与目录 → 勾选 → 标记**:`对内` / `对外` / `不需要`(目录级标记决定整目录)
- 步骤:标注 → 生成修订
- 期望:标记按 **系统设置 → 测试知识库绑定** 分发到对应测试库;`不需要` 不入库
- 证据:`GET /api/v1/source-catalog/routing-capabilities`;绑定未配时标记被拒
- 状态:✅ 本期实测(界面勾选 → "对内" → 目录刷新完成;`internal_enabled/external_enabled` 均 true)
### A3 生成修订 / 新版本(发布准备)
- 入口:**发布管理 → 生成新版本** → `批准` / `驳回`
- 期望:只有批准后的变更集才能进入密封与批次
- 证据:变更集版本号与审核动作记录
- 状态:✅(见 `P5_REVIEW_AND_SEAL_ACCEPTANCE.md`)
- **⚠️ 顺序约束(2026-10-10 实测踩到)**:`生成新版本` 是**生成即固化**的——它把当时的
"测试是否就绪 / 是否批准"快照写进条目。若在**处理完成/审核批准之前**生成,则该条目会被
记为 `not_ready`,且此后 `确认冻结` **不会重算**(冻结沿用生成时的条目),
重生成才是唯一的刷新方式。正确顺序:**处理完成 → 批准 → 生成新版本 → 确认冻结 → 建批次**。
实测:批准前生成 v2(新增1/阻断195,本条 not_ready)→ 批准后重生成 v3(**新增2/阻断194**,
本条 `ready/can_release=t`)→ 冻结 → 定批次 ✓
## B. 入库与监控
### B1 待入库 → 开始处理 → 完成
- 入口:**文档工作台**(待入库列表)→ 选中 → **开始处理**
- 期望:上传 → 解析 → 索引 → `completed`;目标库里能看到知识条目与 wiki 页
- 证据:单文档 E2E `tp-3056261d46396a0695497f38421346b3` → `processing_state=completed`;知识 `cf0af40f-7c0c-4526-ad6a-4515b3b2ca00`(4 chunks,wiki 9 页)
- 证据(2026-10-10 界面实测):`解析监控 → 待入库(1)` → **开始处理** → `tp-c3412382b66f55404d2b51672ffd0549`
→ `parse_status=completed`、`ingest_state=uploaded`、`poll_sequence=4`、`last_remote_status=completed`;
知识 `98be235b-68cf-43e1-94ca-419f5b97fddb`;任务链 `test.upload` → `test.poll`×4 → `test.validate` 全 completed ✓
- 证据(2026-10-10 第二遍):`tp-466164a074cb22efb` → `processing_state=completed`(polls=8、remote=completed、
测试库 `0c84a098…`);任务链 `test.upload → test.poll×8 → test.validate` 全 completed ✓
- 证据(2026-10-10 第三遍,PDF/MinerU):`tp-4aad3a6663efe94ccc76b96…` → `processing_state=completed`(polls=6、remote=completed);
审核页给出「知识读取:通过;内容处理:通过;搜索验证:需人工确认」✓ → **PDF/MinerU 解析链路通**
- 状态:✅ 2026-10-08 / 2026-10-10 实测(三份小文档:docx 转换路 + md 直下路 + pdf/MinerU 解析路)
### B2 解析监控与补救
- 入口:**解析监控** → 处理中 / 需要你出手 / 需下架 / 死信 / 已完成 / 知识库差异 / 知识库解析进度
- 步骤:看"需要你出手" → 重排失败项 / 处理需下架项
- 期望:失败可重排;"需下架"列出仍留在库里的禁止项
- 证据:死信与重排链路(`cddc4c9`)
- 状态:✅ 链路|⚠️ "重排"按钮行为目前只有单测覆盖(页面当时无失败项可点)
- **2026-10-10 界面实测(第三遍:禁止导入 → 需下架)**:把已入库的 `DH-AG95夹爪适配-指令协议.md` 改成「禁止导入」
→ 回执「策略已保存 1 份、标记禁止 1 份、取件 0、待入库 0」✓,目录表格变为「禁止导入」✓
- **发现真问题(已修)**:`需下架` 车道恒为 0。车道 SQL 判 `retrieval_state='enabled'`,但 `retrieval_state`
在这套状态机里描述的是**是否发布到生产**(`unpublished/disabling/disabled/failed`),全仓没有任何代码写过 `'enabled'`
—— 实测线上 5/5 发布单元都是 `unpublished`,界面上「仍在知识库里的文件会出现在这里」的承诺不成立。
修为与设计注释一致的口径:**仍在测试库存活(`completed` + 有 `knowledge_id` + 未 `disabling/disabled`)+ 来源项 `policy_state='blocked'`**;
线上库对照:旧判据 0 → 新判据 1,命中正是那支被禁止导入的 `.md` ✓;页面随即显示 `需下架(1)· 对内知识库 · 1 份仍在检索中` ✓
- **仍未闭环(待排期)**:① 车道只按库计数,**没有条目级列表**;② 移除动作(`test.remove` 工作流,已实现且 worker 已注册)
目前只由变更集删除路径触发,**页面没有入口**;③ 该工作流是 `DeleteKnowledge`(从测试库删知识条目,重新标注可再入库),
与旧文案"停用检索(可恢复)"的措辞不同 —— 文案已按事实改写(`WeKnoraMonitor.tsx` / `SourceCatalogBrowser.tsx`)
## C. 发布与回滚
### C1 审核 → 密封 → 建批次 → 锁定
- 入口:**发布管理 → 创建草稿批次 → 密封 → 锁定**
- 期望:密封后批次不可改;锁定后才能授权生产发布
- 证据:批次 `rb-9678722c…`(1 项);变更集 `rcs-832f0cf9…` v1(196 项:1 可发布 / 195 阻塞)
- 证据(2026-10-10 界面实测):`批准` → `生成新版本`(v3 `rcs-69c1810b…`,新增2/阻断194)→ `确认冻结`
(`state=confirmed`、`confirmed_by=admin`)→ 勾选本条 + 批次名 `验收-App账号注册-20261010` →
`创建草稿批次` → `锁定清单` → 批次 `rb-d6eb1c91ee49ab3aedaa16d3c3c91f85`(**sealed**、1 项、
绑定 v3、`review_id=rv-e4b38040…`、`action=upsert`、`decided_visibility=internal`)✓
- 证据(2026-10-10 第二遍,`.md`):批准 `.md` → `生成新版本`(v4 `rcs-2b22276e…`,**新增3/阻断193**)→ `确认冻结`
→ 批次 `rb-f1819fb0c4196ae6e22e600874954a0c`(`验收-DH-AG95指令协议-20261010`,**sealed**、1 项、
仅 `tp-466164a074cb22efb`、绑定 v4)→ `锁定清单` ✓;顺序规则再次验证:批准前生成的 v3 里该条仍 `not_ready`,
批准后重生成 v4 才 `ready/can_release=t` ✓
- 证据(2026-10-10 第三遍,PDF):v5 `rcs-8869e6f0…`(**新增4/阻断192**,含 `相机线束固定孔位尺寸图.pdf` `ready`)
→ 批次 `rb-9dfcff203f5fc5e0b4063d3d8b09410b`(`验收-相机线束孔位图-20261010`,**sealed**、1 项)→ `锁定清单` ✓
- 状态:✅ 2026-10-08 / 2026-10-10 实测(三份文档各一次)
### C2 授权发布(双闸 + 页面确认语句)
- 入口:**发布管理 → 授权发布** → 逐字输入确认语句 `publish-to-production` → 确认执行
- 期望:执行 `completed` + `verified`;目标库出现该文档;不出现"一键发布"
- 证据:执行 `rx-dd47920a5b5f5019af4f197fd0c2f92c`;目标库 `0b217e8a…` 文档数 2069→2070;两个闸门 `PRODUCTION_RELEASE_REQUESTS_ENABLED` / `PRODUCTION_BACKUP_RESTORE_VERIFIED` 均为 true
- 状态:✅ 本期实测(页面通道由管理员会话记录操作人;另支持 0600 Bearer 通道)
- **2026-10-10 界面实测(含首个真实跨环境 wiki 发布)**:授权前留档 2m52s → `guardcheck` 两库 PASS
→ 输入 `publish-to-production` → 确认执行
- 首次执行**失败**:项级 `release_verification_failed`,worker 日志
`release wiki replace scope in_scope=0 foreign=8 foreign_ref_stripped=0 untouched=1 skipped_library=1 degraded=true`
→ **已定位并修复**:写入侧(`replaceWiki`)按设计保留目标库**外来源** wiki 页,
而校验侧 `VerifyWiki` 仍要求"生产页集合与包内快照完全相等" → 跨来源共库必然误判。
修复:校验改为与写入侧同一套归属口径(共用 `packageWikiOwners`)——
"快照内页必须都存在 + 多出来的页只有在**不属于本包**时才放行",本包拥有却残留/缺失仍判失败,
撤权仍禁止任何页引用被撤权文档(提交 `eabe55b`)
- **重排后完成**:执行 `rx-63b815604766c750f95c6bba8682a4fe` → `completed`(1/1、failed 0、
项级 `verified`、`production_revision=rv-e4b38040…`、`bundle_sha256=19a85aea…`),
worker 日志 `in_scope=1 foreign=8 … degraded=true` 无失败行
- 页面原文:该执行 `发布 已完成 1/1`;每项结果 `App账号注册.adoc → 发布并验证通过 · 目标库 0b217e8a… · 生产可用` ✓
- **第二遍(2026-10-10,`.md`)修复后首次即成功**:留档 `20261010T023538Z-0b217e8a`
(PG knowledges 3830→**3831**、wiki_pages 1982→**1983**,与上一轮发布/回滚后的库状态吻合)→ `guardcheck` 两库 PASS →
执行 `rx-bc714172a3b0c77885ef18a30dd3ac6b` → **`completed` 首轮通过**(1/1、failed 0、项级 `verified`),
worker 日志同样 `in_scope=1 foreign=8 … degraded=true` 且无失败行 → 页面
`发布 已完成 · 1/1`、每项结果 `DH-AG95夹爪适配-指令协议.md → 发布并验证通过 · 生产可用` ✓
- **第三遍(2026-10-10,PDF)**:留档 `20261010T030026Z-0b217e8a`(PG knowledges 3831→**3832**、wiki_pages 1983→**1990**)
→ `guardcheck` 两库 PASS → 执行 `rx-7044688b5b9432c1cb04f8cf251c3679` → **首轮 `completed`**
(1/1、failed 0、项级 `verified`、`production_revision=rv-aac1a725345c7dbf56c03bde1f5d5442`);
worker 日志 `in_scope=8 foreign=8 … degraded=true` 无失败行(这次包内 wiki 8 页,归属门禁在更大页集上同样正确)✓
- **⚠️ 第三遍踩到真问题(已修,见 F.3g)**:授权发布被回绝 `production release requests are disabled` ——
因为我用 `deploy-api-web.sh` 重建 api 时没叠 `compose.release-gates.yaml`,两道生产门禁被静默关掉;
补上门禁文件重新 up 后重试即成功 ✓(页面当时只在点击处回一句英文错误,很容易误判成功能坏了)
### C2b 下架(delete 动作)★2026-10-10 首次实测
- 入口:**发布管理 → 发布计划** 里对「删除」行勾选 **「选择删除…」** → 勾选 **「确认删除生产知识」**(二次确认框)
→ 填批次名 → **创建草稿批次** → 锁定 → 留档 → **授权发布**(同一固定语句 `publish-to-production`)
- 期望:执行项 `action=delete`、`production_revision=absent`、`state=verified`;目标库该知识**停用**(行保留,可恢复)
- 证据(2026-10-10):先把 PDF 重新发布使生产侧"存在"(`rx-80ad81d5…`,目标行 `enable_status=enabled`)→
在来源目录把它改为**禁止导入** → 计划给出 `相机线束固定孔位尺寸图.pdf → action=delete / production_state=present / can_release=true` ✓
→ v8 `rcs-8702357179…`(**delete_count=1**、113 项)→ 批次 `rb-8c1ad1c9ba849c7cfe3a5045b53eb880`
(`验收-下架孔位图-20261010`、**sealed**、1 项、`action=delete`、绑定 v8)→ 留档 `20261010T033100Z-0b217e8a` + 门禁 PASS
→ 执行 `rx-6c19232a4ce1a19f937292a0a60b6c84` → **`completed`**(1/1、failed 0、项级 `verified`/`delete`、
`production_revision=absent`);worker 日志 `in_scope=11 foreign=8 … degraded=true` ✓
→ **独立核对目标库**:`相机线束固定孔位尺寸图.pdf.pdf` 行仍在、`parse_status=completed`、
**`enable_status=disabled`** ✓(下架=停用检索、不删行,可恢复)
- ⚠️ 过程中踩到并修掉 **Bug #6**(见 F.3h):禁止导入只写 `source_items.policy_state`,而**两处**"期望策略"解析
(计划 + 建批 delete 就绪判定)都不看它 → 计划不给 delete、建批直接 422
### C9 数据源配置:列出来 → 勾选 → 变成来源 ★2026-10-10 面板实测
- ① 来源卡片不再做文件格式筛选(格式由「来源与目录」的目录级标注决定,就近覆盖上级)。
- ②③ 「可加入的来源」按当前钉钉账号列出**知识库**与**钉盘空间**:
知识库 18(组织 17 + 我的知识库 1)、钉盘 7(我的文件 + 6 个组织空间);
已登记的三条钉钉来源自动标「已是来源」并禁用勾选。
列不出来时给原因而不是空白:dws 未登录/权限不足 → 503 + 提示;钉盘被拒时只丢钉盘那一栏
(`drive_notice`),知识库照常显示;被上限截断时提示"可能不全"。
- ④ 勾选 → `加入为来源(选中 N)` → 走既有登记路径,首次目录扫描自动排队。
实测:勾选「视觉文档」→ 新来源 `sc-8a4be5b8293d1c9a048c3c7e9b194155`(dingtalk、enabled)
+ `source.catalog.refresh` 已完成。扫码会话失效时会直接弹二维码,扫完自动重试本次登记。
- 后端:`GET /api/v1/dingtalk-spaces/available`(管理员、只读,dws)。api 与 worker 共用同一份
dws 状态(bind mount 同一份:refresh token 轮换制,不能各留一份)。
### C8 一键发布(建批+锁定+授权合成一个动作)★2026-10-10 实测
- 入口:审核行上的 **`发布到生产`**(管理员 + 生产门禁开启时显示),或工具栏「发布到生产(选中 N 项)」。
- 证据:点行级按钮 → 确认弹窗输入固定语句 → 提交 → 库内一次得到:
批次 `rb-bee80550b6fae758a4b1076db1c6bc36`(`立即发布 · 2026/10/10`、**sealed**、1 项、绑定冻结版本)
+ 执行 `rx-3d831129e01322a8dfcf08b09cd4adbe`(publish、**completed**、项级 `verified`、`production_revision=rv-934f2a64…`)
→ 随后在页面上回滚:`rx-43835fac7d218c008933f1cb93c75b2b` → `rolled_back`、项级 `production_revision=absent`
(回滚状态由适配层读回校验 `verify restored` 后才置位)。
- 与之前对照:同一条文档从"已批准"到"发出",此前需 找条目→勾选→填名→建批→滚到清单→找批次→授权→输语句→提交(~9 次交互);
现在 **3 次**(点发布 → 输固定语句 → 提交)。
- 约束未放松(服务端测试固化):请求门禁关闭 → 403;备份恢复验证未过 → 412;非管理员 → 401;错语句/空选择 → 400;
建批失败(门禁不通过)→ 422;密封后不满足生产门禁 → 409。
### C7 批量审核 + 建批即锁定 ★2026-10-10 实测(发布管理减负 P0-3)
- 界面:审核区块新增「全选可批准(N)」「批量批准选中(N)」「批量驳回选中(N)」;建批按钮改为「创建并锁定清单」。
- 证据(建批即锁定):勾选 2 条已批准 → 填名 → 一次点击 → 批次 `rb-95210ddcc14bbcb6afee323acd87b945`
(`验收-批量两项-20261010`)出来就是 **`sealed`**(2 项、`sealed_by=admin`),**没有单独的"锁定"这一步**;
且复用同一份冻结版本 `rcs-028ac704…`(计划摘要未变 → 同版本复用)。
- 判据修正:界面「可批准」原先把**已驳回**的也算进去(`review_state !== 'approved'`),已改为只含 `pending` + 门禁通过——
已驳回的不会被"全选"顺手翻回批准。
- 语义边界(集成测试固化):批量批准按人工身份写入,人工可推翻自己先前的驳回;
`system:auto` 发起的批量批准对人工驳回过的那条必须跳过(`Decided=0 / Skipped=1`)。
### C6 建批时冻结 + 自动批准 ★2026-10-10 实测(发布管理减负 P0-1 / P0-2)
- 期望:**不再有** `生成新版本` / `确认冻结` 两步;对内知识验收通过后由系统自动批准;建批即绑定一份已确认版本。
- 证据(P0-2,界面 + 库):界面里给 `ATOM-备忘录笔记.adoc` 标「对内」→ 待入库 → `开始处理`;
处理完成(`completed` / `unpublished`,验收 `chunks_present=passed`、`knowledge_exists=passed`、`retrieval_hit=manual_required`)
→ 审核记录 **`rv-934f2a64…` / `approved` / `reviewer_ref=system:auto` / `approved_for_release`**(未点任何人工批准)
- 证据(P0-1,界面 + 库):勾选该条 → 填名 → `创建草稿批次`(**一次点击,无任何冻结步骤**)
→ 批次 `rb-a1c0012153e476f9a37e9c6d5f97cbe4`(`draft`)自动绑定 **`rcs-028ac7045f9d2e533b28402b391accdf`**
(`confirmed`、112 项、绑定时刻确认)→ 清单内含 `ATOM-备忘录笔记.adoc / add / can_release=true`
- 对照:同一场景(批准晚于上一次冻结)在今天上午必然 422 `release change set must be confirmed and cover every requested publication`
- 单文档交互次数:今天上午 ~9–11 次(含两次踩顺序陷阱)→ 现在 **~4–5 次**(勾选、填名、建批、锁定、授权)
### C5 测试库侧移除(解析监控 → 需下架 → 逐条下架)★2026-10-10 首次实测
- 入口:**解析监控 → 需下架** 车道逐条列出「来源侧被判禁止导入、但知识仍存活在测试库里」的条目
(`GET /api/v1/test-publications?removal=required`,口径与车道计数同源)→ 每条 **下架(从测试库移除)**
→ 页内二次确认(**取消在左、确认在右**)→ 走既有 `test.remove` 工作流(删除测试库知识并读回校验)
- 期望:条目置 `disabling` → 工作流完成后 `disabled`;车道计数递减;生产知识库不受影响
- 证据(2026-10-10,两条都经界面走完):
- 点击前车道 `需下架(2)`:`相机线束固定孔位尺寸图.pdf`、`DH-AG95夹爪适配-指令协议.md`(与直接查库的候选一致)
- 第 1 条(PDF):点下架 → 车道回到 1 → `test_publications.retrieval_state=disabled`(`03:57:03Z`);
`jobs` `test.remove` / `completed` / attempt 2(第 1 次 `test_remove_pending`=远端仍在处理,重试通过);
工作流以 **`GetKnowledge` 读回 404** 判成功
- 第 2 条(md):点下架 → **确认态出现但未执行**(核库:仍 `unpublished`、移除任务数仍 1)→ 点确认
→ `disabling` → `disabled`(`04:08:55Z`);job `job-67e7e62c…` `completed`(attempt 2)
- 界面终态:`需下架(0)` + 空态文案;页面提示「已受理下架…」
- ⚠️ 过程中踩到并修掉 **Bug #7**(二次确认按钮出现在原按钮位置,连点即误执行;已把「取消」放左)
与 **Bug #8**(`jobs` 部分唯一索引 + 空串 business_* → 第二次移除被静默吞掉、条目永久卡死;已改 NULL + 落行校验)
### C3 查看结果 → 回滚
- 入口:**发布管理 → 查看结果** → 需要时 **回滚** → 确认语句 `rollback-production-release`
- 期望:回滚后目标库该知识停用(**不删知识行**),wiki 页保持
- 证据:`rx-f1d1d669…` → `rolled_back`,`authorized_by=admin`,`prior_execution_id=rx-dd47920a…`;目标库该知识 `enable_status=disabled`、chunks 4/0 enabled、wiki 9 页完整
- 证据(2026-10-10 界面实测):输入 `rollback-production-release` → 执行 `rx-71d13453f5c684a7fa6f9c8337610d78`
→ `rolled_back`(1/1、failed 0、项级 `rolled_back`);页面 `回滚 已回滚`、每项结果
`App账号注册.adoc → 已恢复上一版本 · 目标库 0b217e8a… · 已恢复`;
**独立核对目标库**:`knowledges` 行仍在、`parse_status=completed`、`enable_status=disabled` ✓(首发布回滚=停用,不删行)
- 证据(2026-10-10 第二遍,`.md`):`回滚` + `rollback-production-release` → 执行 `rolled_back`(1/1、failed 0、项级 `rolled_back`);
**独立核对目标库**:行仍在(title `DH-AG95夹爪适配-指令协议.md.md`)、`parse_status=completed`、`enable_status=disabled` ✓
- 证据(2026-10-10 第三遍,PDF):执行 `rx-21be23f095075701aba8a62d58f3dd7b` → `rolled_back`(1/1、failed 0);
**独立核对目标库**:`相机线束固定孔位尺寸图.pdf.pdf` 行仍在、`parse_status=completed`、`enable_status=disabled` ✓
- 状态:✅ 2026-10-08 / 2026-10-10 实测(三份文档各一次发布+回滚,目标库均为"停用不删行")
### C4 重排失败执行
- 入口:**发布管理 → 失败执行 → 重排** → 确认语句 `retry-production-release`
- 期望:只重置失败项;`verified` / `rolled_back` 项保持完成;重排不改写 `AuthorizedBy`(历史不可篡改)
- 证据:`internal/httpapi/production_action_test.go` 双通道用例
- 状态:✅ 单测覆盖 + **2026-10-10 真实失败项实测**(上面 C2 的失败执行即用 `重排` +
固定语句 `retry-production-release` 重跑成功;同一条执行号 `rx-63b81560…` 由 `failed` 转为 `completed`,
`authorized_by=admin` 未被改写)
## D. 系统设置
### D1 服务配置(测试 / 生产端点与 ID)
- 入口:**系统设置 → 服务配置**(管理员)
- 规则:只配四项(测试地址、生产地址、生产 ID、知识库 ID);**保存时真探测**,无效才拒;凭据只写不读
- 状态:✅(见 `P41/P42` 与 `ui_settings_*` 测试)
### D2 测试知识库绑定(对内 / 对外)
- 入口:**系统设置 → 测试知识库绑定** → 选库 → 保存绑定
- 期望:保存立即生效并重排"还没上传"的待入库项;对内 ≠ 对外
- 状态:✅ 本期实测
### D3 生产知识库绑定(源库 → 目标库)
- 入口:**系统设置 → 生产知识库绑定** → 对内/对外各选一个生产库
- 期望:发布写入被冻结到该项的 `target_knowledge_base_id`;未绑定时按同 ID 语义并在契约检查处失败关闭
- 状态:✅(`P8_PRODUCTION_KB_MAPPING_ACCEPTANCE.md`;迁移 000044)
### D4 存储与备份(整库逻辑备份的定时)★本期新增
- 入口:**系统设置 → 存储与备份**(管理员)
- 步骤:设置 **定时时间** + **储存天数**(+ 每月归档保留、启用开关)→ **保存并下发到主机**;另可 **重新下发当前设置** / **立即备份一次**
- 期望:保存即写库并下发到目标主机 cron;页面显示主机上**实际生效**的计划、cron 条目、最近一次备份与"已一致/待下发"
- 证据(面板原文):`目标主机 weknora-test · 已连接`、`主机 cron 服务 active`、`主机 cron 条目:30 03 * * * root /bin/bash /opt/weknora-test/logical-backup.sh >> …`、`主机上生效的计划 每天 03:30 · 保留 7 天 · 每月 3 份`、`与设置是否一致 已一致`、`最近一次备份 20261009T060736Z / 34 秒 · 742.7 MiB / 覆盖 8 库 / 失败 无`
- 实测改动往返:面板改 `04:05 / 14 天` → 主机 `/etc/cron.d/kb-logical-backup` 变 `05 04 * * *`、参数文件 `TIME=04:05 / KEEP_DAYS=14` ✓ → 再改回 `03:30 / 7 天` → 主机 `30 03 * * *` ✓
- 立即备份:按钮变"备份进行中…"→ 约 50 秒后自动刷出新结果(异步 202 + 在途单飞,重复点击不会跑两次)
- 状态:✅ 2026-10-09 面板实测(写操作仅管理员:`PUT/POST /api/v1/backup-schedule*`)
### D5 刷新策略
- 入口:**系统设置 → 刷新策略**(文档工程师可改)
- 期望:文档/资源刷新间隔、停滞判定、finalizing 时长;保存先验证再原子启用、旧版本留审计
- 状态:✅(`ui_settings` 版本化激活测试)
### D6 成员与角色
- 入口:**系统设置 → 成员与角色**(管理员)
- 期望:成员增删、角色(viewer/engineer/admin);管理员初始密码首登强改
- 状态:✅(`users` 接口与角色网关)
## E. 备份与恢复(运维流程)
### E1 发布/导入前留档(PG 行 + ES 文档)
- 入口:命令行 `bash scripts/target_kb_snapshot.sh`(默认云内执行)
- 期望:`$KB_BACKUP_ROOT/target-kb-snapshots/<UTC>-<kb8>/` 出现 8 张表 JSONL + `es_documents.jsonl.gz` + `MANIFEST.json`(sha256/每库 ES 计数) + `export.sql` + `README.md`
- 证据:实测 **2 分 52 秒**(PG 8 表 44 秒;两库 ES 131,122 条 / 692 MB);`PULL_ES=1` 拉回本机 **19 分 40 秒**
- 状态:✅ 2026-10-09 实测
### E2 发布前硬门禁
- 期望:写目标库之前必须存在覆盖该库、24h 内、**PG 与 ES 两层齐备且哈希可校验**的留档;否则 `release_target_snapshot_missing`(不可重试),目标库一行不碰
- 证据:`go run ./cmd/guardcheck backups/target-kb-snapshots <kb_id>` → 两个绑定库 `PASS`、未绑定库 `BLOCK`;门禁单测 10 条
- 状态:✅ 已上线(release-worker 已带此门禁)
### E3 回填演练(证明"存了能回去")
- 入口:`python3 scripts/restore_drill.py --snapshot-dir <留档> --target-url <本地测试库>`
- 期望:行数与清单逐一致、抽样页面正文/标题逐字段相同、清理后残留 0
- 证据:**13,539 行**全部回填成功;8 张表与清单逐一致、冲突 0、抽样页 388 字符一致、残留 0(实测值)
- 状态:✅ 2026-10-09 实测
### E4 整库逻辑备份(自管,落自己盘上)
- 入口:主机 `bash /opt/weknora-test/logical-backup.sh`(手动)或按 D4 的定时
- 期望:`$KB_BACKUP_ROOT/rds-logical/<UTC>/<库>.dir`(`pg_dump -Fd -j4 -Z6`)+ `MANIFEST.json`(含 `failed_databases`);按天轮转
- 证据:**33 秒 / 742.6 MiB / 214 文件 / 失败 0**,覆盖 8 个库;RDS 管理库 `rdsadmin` 自动跳过;中断的运行改名 `*.incomplete`
- 状态:✅ 2026-10-09 实测
### E5 其余三层(现状)
- **ES 快照**:仓库 `kb_backup`(宿主 `$KB_BACKUP_ROOT/es-snapshots`)+ SLM `kb-hourly`(3 天)/`kb-daily`(7 天);首个快照 **36 秒 / 4.7 GB / 0 分片失败** ✅
- **OSS 版本控制**:桶 `dobot-weknora-test` 已开启(覆盖/删除可按 VersionId 回滚)✅
- **RDS 托管备份**:由控制在控制台自管(按你的决定:本期不做,不阻塞)⏳
- **备份目录契约**:`scripts/backup_layout.sh` 定义 `/srv/kb-backups/{target-kb-snapshots,rds-logical,es-snapshots,logs}`,换主机(未来的生产云主机)跑同一份 ✅
## F. 本期已知限制与边界(验收时不必当缺陷)
1. **ES 留档字节默认留目标主机**(`PULL_ES=0`);异地副本不做(你的决定)。
2. **ES 快照仓与留档同机同盘**:整机故障需靠 RDS 托管备份 + OSS 还原。
3. **wiki 通道的真实跨环境发布已跑通**(2026-10-10,见 C2):发现并修复"写入保留外来源页、校验却要求集合相等"的口径冲突(`eabe55b`);降级保留(`degraded=true`)现在是发布常态路径。
3b. **指标条的健康探测有误报**:`解析监控` 顶部把 Embedding 模型 / LLM 模型 / OSS / Redis-MQ / WeKnora API 显示为"阻断",但本期入库与发布全链路(上传/解析/索引/发布/回滚)都实际成功 → 探测口径待校正(不作阻塞项)。
3c. **`系统设置 → 服务配置` 的"测试端"探测报 404**:`test_weknora_base_url` 为空时 api 用 `TEST_WEKNORA_BASE_URL=http://app:8080` 探测,而 `app` 在治理栈网络内不可解析 → 显示"可达·不可接单";worker 走的是可用路径(本清单 B1/C2 均成功),仅探测口径问题。
3h. **「禁止导入」未进入发布计划的期望策略 → 生产知识下不了架(已修)**:禁止导入写的是
`source_items.policy_state='blocked'`,但"期望策略/desired_policy"的解析只看
`source_item_scope_overrides` / `directory_scope_rules` / `source_items.visibility`,**两处都不看 policy_state**:
① 发布计划(`release_plan.go`)把被禁止且生产侧已存在的条目算成 `unchanged/present`、`can_release=false` → 永远不给 delete;
② 建批的 delete 就绪判定(`release_batches.go` 的 `releaseDeleteReadyCTEs`)同样口径 → 即使计划给了 delete,创建封批仍被拒 **422**
(实测原文:"创建封批失败(HTTP 422)")。修复=两处都把 `policy_state='blocked'` 算进期望策略(同一口径,各一行);
新增集成测试用 **policy_state**(真实路由的写法)复现,并同时断言"计划出 delete"与"delete 批次可创建",
回退任一处修复该测试即 FAIL ✓。附带效果:从未导入却被标为禁止的 82 条"等待测试"噪音不再占发布计划(195 → 113 项)。
注意:现有 release_authorization 集成测试用 `source_item_scope_overrides` 造 blocked(计划能识别),
所以一直没暴露这个"测试与现实写法不一致"的偏差。
3g. **`deploy-api-web.sh` 会静默关掉生产门禁(已修)**:脚本的 `COMPOSE_FILES` 默认只叠 `compose.yaml`,
重建 api 时若不叠 `compose.release-gates.yaml`,`PRODUCTION_RELEASE_REQUESTS_ENABLED`/`PRODUCTION_BACKUP_RESTORE_VERIFIED`
变 false,生产授权接口静默关闭,页面只在点「授权发布」时回一句 `production release requests are disabled`。
修复:脚本加**前置保护** —— 正在运行的 api 已开闸而本次未叠门禁文件时直接拒绝
(`REFUSED_WOULD_CLOSE_PRODUCTION_GATES`,并打印保留门禁的正确命令;确要关闸显式 `ALLOW_GATE_OFF=1`),
结尾新增 `=== 7b` 回显重建后的两道门禁状态。三种情形已按同一段判定逻辑验证 ✓
3f. **集成测试曾改到线上设置(已修 + 已恢复数据)**:`internal/httpapi/ui_settings_integration_test.go` 只判断
`TEST_DATABASE_URL` 是否存在、**没有 schema 隔离**,而它就做「创建候选并激活」——直接跑在共享/线上库上时,
会把真实的 active 设置挤成 retired。2026-10-10 排查车道问题时踩到:线上 active 变成测试负载
(30/60/3600/14400 + `https://weknora.prod.example:8443/weknora`),真实的 `uis-d40ea1fc…`
(15/30/1800/7200 + 生产端点 `https://weknora-test.dobot.cn/`)被 retired。
处理:先备份 `backups/ui-settings/ui_setting_revisions.pre-restore.*.jsonl`,再用单事务(先 retire 再 activate,
唯一约束 `uq_ui_setting_active` 只允许一条 active)恢复,两条 update 各影响 1 行,并复核线上值 ✓;
根因修复=给该用例加 per-test schema 隔离(照 `internal/monitoring`),复跑后线上设置不再被改动 ✓。
**规矩**:任何"创建并激活"的集成测试必须建独立 schema;只判 `TEST_DATABASE_URL` 存在就写共享库的用例一律视为危险。
3i. ~~**测试库侧的移除仍无页面入口**~~ **已闭环(2026-10-10)**:解析监控 → 需下架 现在逐条列出条目,
每条可「下架(从测试库移除)」并二次确认(`POST /api/v1/test-publications/{id}/removal`,engineer 权限、
写路由需 `Idempotency-Key`、按条目幂等);实测两条走通(见 C5)。
3e. **发布/回滚结果页不自动刷新**:Release Worker 完成后,`发布管理` 的执行行仍显示"待执行"(首次与第二遍都遇到),
需手动 `刷新当前页面`/reload 才看到 `已完成`/`已回滚`。后端状态是对的(DB 已是终态),属展示层轮询缺口。
3d. **`release_executions.snapshot_verification_id` 为空**:发布前快照门禁确实生效(缺少/过期会 `release_target_snapshot_missing`),但执行行没有记下"用的是哪份快照",追踪性待补。
4. **"重排"在页面上无真实失败项可点**(单测覆盖);指标条"失败 1"是旧 `artifact.fetch` 死信遗留。
5. 自管逻辑备份**不能替代 PITR**(RDS 控制台那条本期不做)。
---
## 验收建议顺序
1. **D4 存储与备份**(本期新增,步骤最短、证据最全)→ 2. **E1/E2/E3/E4**(留档→门禁→回填→整库)→ 3. **C2/C3**(授权发布 → 回滚,需闸门开启)→ 4. **B1/B2**(入库与补救)→ 5. **A1–A3、D1–D3、D5、D6**(既有流程,按各自专题文档逐条核)。