feat(release): 批量发布/建批即锁定 + 需下架逐条移除 + 数据源勾选改造(23 提交) #2

Closed
zheromiku wants to merge 0 commits from feat/release-batch-and-monitor into main
Owner

范围

分支 feat/release-batch-and-monitor,23 个提交、80 个文件,来自本地 main 上尚未推送的开发成果。

分组:

  • 发布管理:建批即锁定 + 验收通过自动批准(把「每份文档 8 步」砍两步)、批量审核、发布到生产一键完成
  • 需下架车道:可逐条移除测试库知识(补齐最后一个没有页面入口的流程)
  • 数据源配置:改成「列出来 → 勾选 → 变成来源」,下线「+ 新增钉钉知识库」按钮
  • 修复:wiki 校验/写入归属口径不一致(跨环境发布必然失败);白名单环境漏传 dws 变量(钉钉取件整体失败);
    需下架判据用了没人写的状态(恒为 0);重建 api 会静默关掉生产门禁
  • 文档:docs/OPERATIONS_RUNBOOK.md、docs/USER_FLOWS_ACCEPTANCE.md 及验收记录
  • 仓库卫生:新增提交撤出误入库的 node_modules/.vite/.../results.json 与 8 个历史 scripts/__pycache__/*.pyc,并补上根级 /node_modules/ 忽略规则

安全与数据边界

  • 未改数据库 schema(无 db/migrations/ 变更)
  • compose.yaml(+13 行)与 scripts/deploy-api-web.sh 有改动 ⇒ 按规范 §1.2 属部署相关,需异人评审

提交信息闸门

已按 CI 同一条规则逐条预检本分支全部提交:全部合规(类型/长度/结尾句号)。

真跑过的测试

  • 无。 本地 make verify 未由我复跑;CI(go build/vet/test + npm ci && build)是本次的证据来源。
  • ⚠️ 与规范 §3.1(提交前跑本仓门禁)有偏差,如实记录,不粉饰。

尚未验证

  • 集成测试(internal/platform/postgres/*_integration_test.go 等)在 runner 上是否依赖数据库,未确认
  • 未在测试/生产环境部署任何东西

明确声明

未合并、未部署、生产数据库未改动。 合并与部署是分开的动作,且由人决定。

CI 结果(回填,实测)

  • 头提交 63db2b8d1e7c 上的检查:build / build (push) = success(Successful in 24s)
  • 运行链接:http://192.168.50.223:3000/Weknora/knowledge-governance/actions/runs/35/jobs/50
  • 该检查是分支保护强制要求的哪一条 ⇒ mergeable=True 表示已满足。
  • 由此可证:go build ./... / go vet ./... / go test ./... 与前端 npm ci && npm run build 在该 runner 上通过;
    此前担心的集成测试依赖数据库问题未发生(runner 上未因缺库而失败)。
## 范围 分支 `feat/release-batch-and-monitor`,**23 个提交**、80 个文件,来自本地 main 上尚未推送的开发成果。 分组: - **发布管理**:建批即锁定 + 验收通过自动批准(把「每份文档 8 步」砍两步)、批量审核、发布到生产一键完成 - **需下架车道**:可逐条移除测试库知识(补齐最后一个没有页面入口的流程) - **数据源配置**:改成「列出来 → 勾选 → 变成来源」,下线「+ 新增钉钉知识库」按钮 - **修复**:wiki 校验/写入归属口径不一致(跨环境发布必然失败);白名单环境漏传 dws 变量(钉钉取件整体失败); 需下架判据用了没人写的状态(恒为 0);重建 api 会静默关掉生产门禁 - **文档**:`docs/OPERATIONS_RUNBOOK.md`、`docs/USER_FLOWS_ACCEPTANCE.md` 及验收记录 - **仓库卫生**:新增提交撤出误入库的 `node_modules/.vite/.../results.json` 与 8 个历史 `scripts/__pycache__/*.pyc`,并补上根级 `/node_modules/` 忽略规则 ## 安全与数据边界 - **未改数据库 schema**(无 `db/migrations/` 变更) - `compose.yaml`(+13 行)与 `scripts/deploy-api-web.sh` 有改动 ⇒ 按规范 §1.2 属**部署相关,需异人评审** ## 提交信息闸门 已按 CI 同一条规则逐条预检本分支全部提交:**全部合规**(类型/长度/结尾句号)。 ## 真跑过的测试 - **无。** 本地 `make verify` 未由我复跑;CI(`go build/vet/test` + `npm ci && build`)是本次的证据来源。 - ⚠️ 与规范 §3.1(提交前跑本仓门禁)有偏差,如实记录,不粉饰。 ## 尚未验证 - 集成测试(`internal/platform/postgres/*_integration_test.go` 等)在 runner 上是否依赖数据库,未确认 - 未在测试/生产环境部署任何东西 ## 明确声明 **未合并、未部署、生产数据库未改动。** 合并与部署是分开的动作,且由人决定。 ## CI 结果(回填,实测) - 头提交 `63db2b8d1e7c` 上的检查:`build / build (push)` = **success**(Successful in 24s) - 运行链接:`http://192.168.50.223:3000/Weknora/knowledge-governance/actions/runs/35/jobs/50` - 该检查是分支保护强制要求的哪一条 ⇒ `mergeable=True` 表示已满足。 - 由此可证:`go build ./...` / `go vet ./...` / `go test ./...` 与前端 `npm ci && npm run build` 在该 runner 上通过; 此前担心的集成测试依赖数据库问题**未发生**(runner 上未因缺库而失败)。
zheromiku added 23 commits 2026-10-10 17:43:48 +08:00
- 记录开关/路由/平台侧判定保留、凭据共享与续期 runbook 依赖
- 本人复核:worker 内 dws v1.0.63 接线、鉴权(须 HOME=DWS_HOME,否则误报未登录)、
  真实调用 ok、回归测试 18 项 + 19 断言、CI #6/#7/#8 全 success
按实际干活顺序:开工自检(含 dws 凭据续期)→ 取件 → 标注 → 发布准备 → 待入库处理 →
发布前留档 → 授权发布 → 回滚/重排 → 系统设置维护项 → 备份恢复运维 → 按事故恢复。
末节给出 7 条与旧流程的差异对照。
端到端验收实测(2026-10-10):标注后取件以 artifact_fetch_rejected: credentials 终态失败。
根因:artifactfetch runner 给子进程的是白名单环境(防凭据外泄),但没有透传 dws 取件器
必需的 ARTIFACT_DINGTALK_FETCHER/DWS_BIN/DWS_CONFIG_DIR/DWS_HOME/DWS_DISABLE_KEYCHAIN
→ dws 找不到配置目录与文件型 keychain($HOME/.local/share/dws-cli 落空)→ 报未登录 →
映射成 credentials 终态(不可重试)→ 且不会回落老路径。脚本单跑正常,因为绕过了白名单。

- handler.go: childEnv() 显式透传 dws 变量(仅非空值);失败时把 stderr 尾部脱敏后带进错误,
  否则只能看到裸 code 无法定位(本次排查就卡在这)
- 新增 3 个回归测试:环境透传 / 空值不透传 / stderr 上报与敏感内容抑制
- 实测:修复后重新入队 → artifact.fetch completed,产物 sn-753f….docx 4799B(adoc→docx 导出),
  content_objects 记为 docx/verified
端到端验收实测(2026-10-10):文件型文档(docx)跨环境发布时,写入侧按设计保留了
目标库外来源 wiki 页(日志 in_scope=0 foreign=8 untouched=1 degraded=true),
但校验侧 VerifyWiki 仍要求"生产页 slug 集合与包内快照完全相等",必然误判,项级
报 release_verification_failed,批次随后被判失败单项失败。

- verifier.go: 归属判定与写入侧 replaceWiki 共用 packageWikiOwners;
  校验改为"快照内页必须都存在 + 多出来的页只有在不属于本包时才放行",
  保留原有牙齿:本包拥有却缺失/残留的页仍判失败;撤权仍禁止任何页引用被撤权文档
- wiki_channel_test.go: 用例改为 外来源多余页放行 / 本包拥有残留页拒绝 / 缺页拒绝
- 实测:同一批次重排后完成(done=1/1 item=verified 生产可用),回滚 rolled_back
  这也是 wiki 归属门禁上线后第一次真实跨环境发布
- A1:记录白名单环境漏传 dws 变量这一缺陷与修复后的真实产物证据(d29e04e)
- A3/C1:写明生成即固化的顺序约束——处理完成 → 批准 → 生成新版本 → 确认冻结(冻结不重算)
- B1/C1/C2/C3/C4:补本期实测(tp-c3412382… / rb-d6eb1c91… / rx-63b81560… / rx-71d13453…)
- C2:记录首个真实跨环境 wiki 发布与「写入保留外来源页 vs 校验要求集合相等」的口径冲突(eabe55b)
- F:wiki 跨环境发布不再列为未跑;新增健康探测误报、测试端 404、snapshot_verification_id 空 三项待办
- A1:第二遍取件(DH-AG95夹爪适配-指令协议.md,3513 B)与内容级 sha256 去重的核对方法
  (content_objects 命中已有行时只更新 updated_at,不更新 created_at;必须走
  source_item_versions.content_object_id 关联,不要按 created_at 过滤)
- B1:tp-466164a074cb22efb → completed(polls=8,测试库 0c84a098…)
- C1:v4 rcs-2b22276e…(新增3/阻断193)+ 批次 rb-f1819fb0…(sealed、1 项)→ 再证「批准后才生成」顺序规则
- C2:留档 20261010T023538Z-0b217e8a + guardcheck PASS + 执行 rx-bc714172… 首轮 completed/verified
- C3:回滚 rolled_back + 独立核对目标库 enable_status=disabled(行不删)
- F:新增待办 3e——发布结果页不自动刷新(worker 已完成仍显示待执行,需手动刷新)
界面验收第三遍(禁止导入 → 需下架)暴露两处真问题:

1) 需下架车道死掉:判据 `retrieval_state='enabled'`,而 retrieval_state 在这套状态机里
   描述「是否发布到生产」(unpublished/disabling/disabled/failed),全仓没有任何代码写过
   'enabled',线上 5/5 发布单元都是 unpublished → 车道恒 0,界面「仍在知识库里的文件会
   出现在这里」的承诺不成立。改为与设计注释一致的口径:仍在测试库存活(completed + 有
   knowledge_id + 未 disabling/disabled)+ 来源项 policy_state='blocked'。
   线上对照:旧判据 0 → 新判据 1,命中被禁止导入的 DH-AG95夹爪适配-指令协议.md;
   修后页面显示「需下架(1)· 对内知识库 · 1 份仍在检索中」。新增集成测试覆盖
   存活/未禁止/移除中/已移除/未上传五种情形(真实 Postgres + 独立 schema)。

2) 集成测试改到线上设置(直接踩到并已恢复):ui_settings_integration_test.go 只判断
   TEST_DATABASE_URL 是否存在、无 schema 隔离,而它「创建候选并激活」——跑在共享库上会把
   真实 active 设置挤成 retired。已先备份(backups/ui-settings/…pre-restore.jsonl),再用
   单事务(先 retire 再 activate,uq_ui_setting_active 只允许一条 active)恢复
   uis-d40ea1fc…(15/30/1800/7200 + https://weknora-test.dobot.cn/),并复核线上值。
   根因修复:给该用例加 per-test schema 隔离,复跑后线上设置不再被改动。

同时把两处与事实不符的文案改实话(WeKnoraMonitor.tsx 需下架说明、SourceCatalogBrowser.tsx
禁止导入说明):移除走 test.remove 工作流(删除测试库知识条目,可再入库),入口当前由
变更集删除路径触发,页面尚无独立按钮——条目级列表与下架按钮列为待排期。
界面验收第三遍(PDF)踩到:授权发布被回绝 "production release requests are disabled"。
根因:scripts/deploy-api-web.sh 的 COMPOSE_FILES 默认只叠 compose.yaml,重建 api 时没叠
compose.release-gates.yaml → PRODUCTION_RELEASE_REQUESTS_ENABLED / PRODUCTION_BACKUP_RESTORE_VERIFIED
变 false,生产授权接口被静默关闭(页面只在点击那一刻提示一句,极易误判成功能坏了)。

- 前置保护:正在运行的 api 已开闸、而本次 COMPOSE_FILES 不含门禁文件时直接拒绝
  (REFUSED_WOULD_CLOSE_PRODUCTION_GATES,并打印保留门禁的正确命令;确要关闸用 ALLOW_GATE_OFF=1)
- 结尾新增 === 7b 打印重建后的两道门禁状态,避免"以为开着"
- 已用同一段判定逻辑覆盖三种情形(已开闸+不叠门禁→拒绝 / 已开闸+叠门禁→放行 / 显式 ALLOW_GATE_OFF→放行)

验收第三遍同时确认:PDF→MinerU 解析链路是通的(入库 completed、验收"内容处理:通过"),
发布首轮 completed/verified(in_scope=8 foreign=8 降级保留正常),回滚 rolled_back、
目标库 enable_status=disabled。证据补进 docs/USER_FLOWS_ACCEPTANCE.md。
界面验收实测(2026-10-10):把已发布到生产的 PDF 改为「禁止导入」后,发布通道既给不出
delete、也建不了 delete 批次。

根因:禁止导入只写 source_items.policy_state='blocked',而"期望策略"的解析只看
source_item_scope_overrides / directory_scope_rules / source_items.visibility——
两处实现都不看 policy_state:
  1) release_plan.go 的 source_truth:被禁止且生产侧存在的条目被算成 unchanged/present、
     can_release=false → 永远不会出现 delete 动作;
  2) release_batches.go 的 releaseDeleteReadyCTEs.source_truth:同样口径 → 计划即使给了
     delete,创建封批仍被拒 422(实测原文「创建封批失败(HTTP 422)」)。

修复:两处都把 policy_state='blocked' 算进期望策略(各一行,注释写明成因)。
新增集成测试 release_plan_policy_state_integration_test.go:用 policy_state(真实路由的写法)
造"生产存在 + 已禁止"的条目,断言①计划给出 action=delete/can_release、②delete 批次可创建;
分别回退任一处修复,该测试即 FAIL(已实测两种回退都 FAIL)。

实测闭环:重新发布使生产存在(enable_status=enabled)→ 禁止导入 → 计划 delete(present/
can_release=true)→ v8 delete_count=1 → 批次 rb-8c1ad1c9…(sealed, action=delete) → 留档
20261010T033100Z + 门禁 PASS → 执行 rx-6c19232a… completed(项级 verified/delete、
production_revision=absent)→ 目标库该行 enable_status=disabled(行保留、检索停用、可恢复)。

附带:从未导入却被标为禁止的 82 条"等待测试"噪音不再占发布计划(195 → 113 项)。
证据与说明补进 docs/USER_FLOWS_ACCEPTANCE.md(C2b + F.3h)。
背景:来源侧被判「禁止导入」后,已建好的测试库知识不会自动消失;此前只有变更集
删除路径会入队 test.remove,页面既列不出条目也没有按钮,运维无法闭环(车道只有按
库计数)。

新增:
- GET /api/v1/test-publications?removal=required:条目级列表,判据与车道计数同源
  (superseded_at IS NULL / 有 knowledge_id / completed / retrieval_state NOT IN
  (disabling,disabled) / policy_state='blocked'),过滤是叠加而不是替换。
- POST /api/v1/test-publications/{id}/removal:engineer 级写路由,要求 Idempotency-Key,
  按条目幂等(operator-remove:<id>);置 disabling 并交既有 test.remove 工作流去删除
  远端知识并读回校验。错误映射 404/409(已移除)/409(不可移除)/501(未接线)。
- 界面:需下架车道逐条显示条目 + 「下架(从测试库移除)」,页内二次确认,成功提示,
  完成后计数归零。

修掉过程中踩到的两个真问题:
1. 二次确认的确认态原先经容器 props 传给视图,一次容器改动把三个 props 丢了 ——
   视图永远渲染原按钮、确认步骤静默消失,而所有静态单元测试仍然通过。改为把确认态
   放在视图内部;并把「取消」放在原按钮位置(真实指针连点两下只会落到取消)。
2. jobs 上有部分唯一索引 UNIQUE (business_change_set_id, business_version_id,
   business_target_kb_id, business_action) WHERE business_change_set_id IS NOT NULL。
   本路径给这三列写空串(非 NULL),('','','','remove') 对所有条目是同一个键 ——
   第一条之后每次 INSERT 都撞唯一索引、被 ON CONFLICT DO NOTHING 静默吞掉,留下
   「状态已 disabling、没有任务」的永久卡死条目,而车道恰好隐藏 disabling,运维也
   看不到。改为写 NULL(语义:无变更集身份)+ 冲突目标收敛到 idempotency_key +
   校验 RowsAffected()(0 行回查既有任务;查不到就报错,不提交无任务的 disabling)。
   卡死状态在重试时可自愈。

测试:internal/platform/postgres/test_publication_removal_integration_test.go(两条发布
各自入队、business_* 为 NULL、卡死自愈;把修复退回旧写法该测试即 FAIL,已实测)、
internal/httpapi/test_publication_removal_test.go(权限/幂等键/错误映射/未接线 501/列表
过滤)、前端 testPublications.test.ts + WeKnoraMonitor.test.tsx。

验收(纯界面,两条都走完):解析监控 → 需下架(2)→ 逐条「下架」+ 二次确认 →
retrieval_state=disabled(PDF 03:57:03Z、md 04:08:55Z),jobs completed(attempt 2,
首次 test_remove_pending=远端仍在处理,重试通过;工作流以 GetKnowledge 读回 404 判
成功)→ 车道需下架(0)。核验「确认前不执行」:首次点击后仍 unpublished、移除任务数仍 1。
写操作的服务端一直回 {"error": "<原因>"},但 getJSON/sendJSON 只把状态码拼进错误消息,
运维看到的是「创建封批失败(HTTP 422)」,无从下手。实测真实原因只有两种,且都有明确
下一步:所选知识不在已冻结的发布清单里(先「生成新版本」+「确认冻结」)、或所选知识当前
不满足发布条件。现在把原因带出,并对这两种已知原因直接给出下一步;其他原因原样展示。

背景(本次 422 的根因):ATOM群体广播控制方案使用方法.adoc 的批准时间 06:07:05Z 晚于
已冻结版本 v8 的冻结时间 03:23:48Z,v8 的 113 行里没有它的行,绑定校验因此不通过。
发布计划页显示的是实时计划(所以写着"可加入发布批次"),建批绑定的是已冻结版本——两者
不一致时必须先重新生成版本,这条顺带值得在产品上更醒目地提示。

测试:reviewRelease.test.ts 补 2 条(覆盖清单不足时给出下一步 / 未知原因原样带出)。
背景:发布管理把"逐条核验"的仪式强加给每一份文档。实测一份文档要 9~11 次交互,
且有两处顺序陷阱(冻结后批准 → 建批 422;生成即固化 → 批准被永久钉住)。

P0-1 建批时冻结:
- CreateReleaseBatch 在未指定版本时,自己把当前实时计划冻结成一份**已确认**版本并绑定
  (同一事务;同摘要复用最近版本,planned 或 confirmed 都可复用,因此同一份计划不堆版本)。
- 语义不变:批一旦建立,清单/摘要/验收轮次/目标库绑定全部不可变、回滚按它走;变的只是
  "谁在什么时候按固化键"——从操作员的手动两步变成建批这一下。
- 效果:去掉「生成新版本」「确认冻结」两步;消灭"批准晚于冻结 → 422"这类顺序陷阱;
  前端不再传版本 id,也不再有"所选知识不在冻结清单里"的阻断。
- 计划→清单项的映射抽成 ChangeSetItemsFromPlan 供两处共用,否则两处算出的摘要不同,
  "同一计划复用同一版本"永远不命中。

P0-2 验收通过自动批准:
- 验收三项结论达发布门槛(knowledge_exists/chunks_present=passed,retrieval_hit 与门槛同
  口径)时,由 worker 写入 reviewer_ref='system:auto' 的批准记录,替代"每份文档点一次批准"。
- 策略 TEST_AUTO_REVIEW_POLICY = off | internal(默认,仅对内)| all;值不认识拒绝启动。
  只影响审核这一步:生产授权、双闸与发布前快照门禁不受影响。界面上只读展示当前策略。
- 安全边界(写死在审核 SQL 里):系统只允许写"第一条"审核——人工一旦表过态,机器不得用一条
  更晚的 approved 把人工结论翻回去;人工随时可再审。集成测试去掉该约束即 FAIL(已实测)。
- 自动批准自身的任何失败都不影响验收结论,只上报。

顺带(P1-1 前半,已被 P0-1 取代的部分已删除):
- 写操作失败把服务端 {"error": ...} 的原因带出来,并对已知原因给出下一步(不再只给 HTTP 状态码)。
- 当前的"不在已冻结清单里"标注与就地修复按钮随 P0-1 一起移除(该失败模式已不存在)。

测试:bind_time_freeze_integration_test.go(绑定已确认版本/同计划复用/冻结后不可变/
动作不符仍 NotEligible)、auto_review_integration_test.go(系统只写第一条、人工可再审)、
validate_test.go(对内自动/对外不自动/策略关闭/验收未过不批/自身失败不影响验收)、
auto_review_test.go、config_test 及前端能力解析测试。
实测耗时:本机 Go 全量 26 包通过、前端 178 条通过。
P0-3:
- 批量审核:新增 POST /api/v1/review-candidates/batch-decision,逐条复用单条审核的写入
  路径(同一套合法性校验与"最新验收轮次"绑定),逐条回报 decided/skipped/skipped_ids;
  不可审核的条目只计入 skipped,不打断整批,其他错误整批回滚(不留半成品)。
  界面新增「全选可批准」「批量批准选中」「批量驳回选中」。
- 建批即锁定:建批请求加 seal 字段,服务端在创建后立即执行既有的密封(仍逐项复核门禁,
  失败整批回滚),草稿态不再单独占一步;界面按钮改为「创建并锁定清单」,流程说明从 8 步改 7 步。
- 判据修正:「可批准」原先把已驳回的算进去(review_state !== 'approved'),改为只含 pending + 门禁通过。

语义边界(写进测试):批量批准按人工身份记录,人工可推翻自己先前的驳回;system:auto 发起的
批量批准对人工驳回过的那条必须跳过(Decided=0/Skipped=1)——机器永远不覆盖人工。

测试:review_batch_integration_test.go(批量决定/跳报/重复 id 拒绝/机器不覆盖人工、
建批即锁定)、review_batch_test.go(幂等键、400/409/500/501 映射、服务端盖操作人)、
reviewRelease.test.ts(批量请求体与逐条回报、建批带 seal)。前端 181 条通过。
用户的反馈是对的:上一轮只砍掉了"冻结/锁定"两个按钮,但一条文档从"已批准"到"发出"实际仍要
~9 次交互(找条目→勾选→填名→建批→滚到清单→找批次→授权→输语句→提交),因为"批次仪式"本身
还留在路径上。

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

测试:publish_now_test.go(四道关卡 + 组合调用 + 名称回退)与 reviewRelease.test.ts(请求体/回传/错误原因);
Go 26 包、前端 183 条全过。
用户反馈「界面还是没优化,步骤太多」。上一轮只把动作合成了一键发布,但页面上仍铺着
7 步流程条、步骤编号、引导卡、分区页签和统计卡——用户看到的是"还有很多步要做"。

- 默认视图只剩三块:生产状态条 → 待发布(主操作,每行「发布到生产」+ 批量)→ 发布记录
  (查看结果/重排/回滚)。
- 待审核仅在有待审条目时出现(对内已自动批准,happy path 上通常不出现);已驳回收进折叠。
- 发布计划、下架选择、冻结清单、批次清单全部收进「<details>更多」,仍是完整能力,只是不再挡路。
- 删除 7 步流程条、步骤编号标题、引导卡、分区页签、四格统计卡。
- 保留全部安全信号:门禁/快照状态条、执行阶段与批次进度、审核策略说明、删除二次确认、
  固定确认语句弹窗、"锁定清单 ≠ 上线"。
- 测试:新增"步骤脚手架不得出现"回归断言(7 步流程条/步骤编号/引导卡/页签 class 一律不得渲染);
  183 条前端测试 + tsc 全过。

自测(面板实测):待发布里点「发布到生产」→ 输 publish-to-production → 确认 →
批次 rb-7088588bac30a109cfaae357d4095ffe(sealed) + 执行 rx-bceb1ca9c1ecdd088e541bad2ebe9d7f
(completed 1/1,项级 verified、production_revision=rv-934f2a64724611e0f50cccadf9a30f87);
再在页面上回滚 → rx-2618de118a7efabea34cdb8fdb384c20(rolled_back 1/1、项级 absent)。
用户对 系统设置 → 数据源配置 的四条要求:
① 这里不要做文件格式筛选(格式归目录级标注决定);
② 列出我账号下能看到的知识库;③ 列出钉盘能看到哪些;④ 勾选那些作为来源。

① 来源卡片不再渲染 FilenameFilterEditor(目录级筛选仍在「来源与目录」里,按目录就近覆盖)。
②③ 新端点 GET /api/v1/dingtalk-spaces/available(管理员,只读):用 dws 列举
   - 知识库:wiki +space-list --type orgWikiSpace/myWikiSpace(实测 17 + 1 个)
   - 钉盘:wiki space list --type mySpace/orgSpace(实测 我的文件 + 6 个组织空间)
   实测约束写进命令形态:--limit 上限是 50;翻页必须 --page-all(--max-items 兜底),
   否则超过一页的知识库会被静默截断(被截断时页面显示"可能不全"而不是假装列全)。
   列不出来 ≠ 空列表:dws 未登录/权限不足 → 503 + 原因;某一类失败只丢那一类。
④ 面板勾选后走既有登记路径(/api/v1/dingtalk-spaces),首次目录扫描自动排队;
   已登记的按空间名标「已是来源」并禁用勾选;扫码会话失效时直接弹二维码,扫完自动重试。

基础设施:api 与 worker 共享同一份 dws 状态(bind mount 同一份,refresh token 是轮换制,
不能各留一份)。子进程 HOME 必须指向 DWS_HOME —— 这条是实测踩出来的:漏了会报"未登录",
已有回归测试钉住。dws 子进程沿用白名单环境,库口令等变量不进子进程。

测试:新包 internal/dingtalkdiscovery(fixture 用真实 dws 输出形状):paging 约束、
截断提示、一类失败不连带、全部失败→ErrUnavailable、子进程环境;httpapi 侧 401/501/503/500
与不泄漏内部错误;OpenAPI 契约(400/503 组件)通过。前端 189 条 + tsc 干净。

面板实测:知识库(18)/钉盘(7)都列出来,已有三条钉钉来源自动标「已是来源」;
勾选「视觉文档」→ 加入为来源 → 新来源 sc-8a4be5b8293d1c9a048c3c7e9b194155(dingtalk,enabled)
+ source.catalog.refresh 已完成。
用户反馈:加了钉盘来源后「来源与目录」里看不到。根因不是丢数据,而是列表只列"已经有快照"
的来源 —— 首次扫描要几分钟到几十分钟(钉盘空间 2900+ 个文件夹),这段时间页面上什么都没有。

- 新 Store 方法 ListCatalogScanProgress:已启用 + 还没有 catalog head + 有在跑/排队的
  source.catalog.refresh(三条件同时成立)。进度直接取 worker 写在任务载荷里的同一份计数
  (progress/message/scanned_directories/discovered_directories/scanned_files),不另立口径。
- /api/v1/source-catalog/sources 在既有列表后追加这些行(snapshot_status="scanning" +
  scan_progress/scan_message)。走窄接口按需接线:未接线时安静跳过,不会把旧列表弄坏。
- 前端:左列显示「首次扫描中 44%」,选中后是进度面板(百分比 + 进度条 + 当前文案 +
  已发现文件数),并说明"扫完自动出现目录";有来源在扫描时每 3 秒自动刷新列表,
  扫完那一行自动变成「本地只读快照」。扫描中不拉目录节点、也不显示标注/格式入口。
- 解析器保持严格:snapshot_status 只接受 ready/scanning,scanning 必须带 scan_progress,
  否则整包拒绝 —— 不给"假装 0%"的机会。

测试:postgres 集成测试(真库)覆盖四种不该出现的来源(停用、已有快照、任务类型不符、
历史上已完成的那次任务);前端 parser + 渲染各一条;Go 全绿、前端 192 条 + tsc 干净。

面板实测:左列「公司级营销资料管理 · 首次扫描中 44%」,主区「首次扫描中 · 44% /
正在扫描目录:已完成 1540 个文件夹 / 已发现约 4934 份文件」。
用户:新增钉钉知识库可以去掉按钮了 —— 数据源发现面板已经能列出账号下所有可见的知识库与
钉盘空间,粘贴链接那条路不再需要,留着只是两个入口做同一件事。

- 删除 系统设置 → 数据源配置 标题上的 DingtalkSpaceRegistrar 及其组件/测试文件(不留死代码);
  标题文案改为「从下方「可加入的来源」勾选即可登记新来源」。
- 登记的 API 客户端(registerDingtalkSpace / 扫码会话续登)保持不变:发现面板仍在用。
- 回归断言:数据源配置页不得再出现「+ 新增钉钉知识库」。

测试:前端 191 条(少 1 条 = 被删组件的测试)+ tsc 干净。
面板实测:按钮消失,勾选列表照常(ATOM知识库/视觉文档/公司级技术支持知识库/技术诊断部知识沉淀/
公司级营销资料管理 已标「已是来源」)。
根级 node_modules 没有被任何规则覆盖(只写了带斜杠的 /web/node_modules/,
只管 web 下),于是 node_modules/.vite/vitest/.../results.json 随 a1b46d2
进了库。另有 8 个 scripts/__pycache__/*.pyc 是历史遗留 —— 规则在,但
.gitignore 对已入库文件无效(正本 §1.4-1)。两者一并撤出,保留磁盘文件。

验证:git ls-files 里不再有 node_modules / __pycache__ / .pyc。
上一提交(fc9dd4c)有两处不成立,这里如实纠正:

1. 规则没加上。判断写成了 `"/node_modules/" not in 全文`,而
   `/web/node_modules/` 里就含这个子串 ⇒ 误判为"已存在",跳过了追加。
   改成对整行集合判断。
2. 产物没撤出。`git rm --cached node_modules` 之后又跑了 `git add -A`,
   而此时根级 node_modules 还没被忽略 ⇒ 文件被原样加回索引,净效果为零。
   正确顺序是:先让忽略规则生效,再撤出索引;否则 add -A 会把它搬回来。
   (8 个 .pyc 当时确实清掉了,因为 __pycache__/ 规则本来就在。)
3. 上一提交正文里"验证:git ls-files 里不再有 …"当时是不成立的。

验证(本次真跑):git check-ignore 命中 /node_modules/;
git ls-files 里 node_modules / __pycache__ / .pyc 计数为 0。
zheromiku closed this pull request 2026-10-10 18:13:29 +08:00
zheromiku deleted branch feat/release-batch-and-monitor 2026-10-10 18:13:29 +08:00
Some checks are pending
build / build (push) Has been cancelled

Pull request closed

Sign in to join this conversation.