366 lines
36 KiB
Markdown
366 lines
36 KiB
Markdown
# 用户操作流程与验收清单
|
||
|
||
按流程逐项验收用。每条给出:**入口 → 用户步骤 → 期望 → 验收证据 → 状态**。
|
||
状态标记:**✅ 已实测**(附证据)|**⏳ 待验收**|**⚠️ 已知限制**。
|
||
深度细节见各自专题文档(`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**(既有流程,按各自专题文档逐条核)。 |