Commit Graph

12 Commits

Author SHA1 Message Date
姚章浩 8bb5cd9a62 feat(ops): 目标知识库只读行级快照工具 + 发布前备份约定
发布链不做发布前备份(Prepare 只归档本包、previousRevision 首次为 absent),
门禁 PRODUCTION_BACKUP_RESTORE_VERIFIED 只是人工开关 —— 目标库覆盖前没有现场。

- scripts/target_kb_snapshot.sh:KB 维度只读导出(SELECT + \copy),输出
  JSONL + MANIFEST.json(行数/字节/sha256/覆盖范围/ES 文档数)+ export.sql
  + README.md(恢复步骤)。支持指定或读取已绑定目标库,INCLUDE_CHUNKS=0 出小快照。
- 文档 P6 新增「发布前备份」:覆盖范围、边界(不含 ES/OSS 本体,整库级走 RDS
  PITR)、以及共享真实库发布/撤权/回滚前必跑快照并记入发布单的约定。
2026-10-09 10:05:23 +08:00
姚章浩 6440e3f679 feat(release): 发布目标知识库可选(源库 → 目标库映射)
- 迁移 000044:production_kb_bindings + release_item_executions.target_knowledge_base_id
- 系统设置新增「生产知识库绑定(对内/对外)」:从生产 WeKnora 实际列表选库,
  保存时真校验(不存在拒收;生产端不可达只提示并如实回报),仅管理员可改
- 执行计划按绑定解析目标库并冻结进 release_item_executions;空目标直接拒收;
  已发布过的条目保留原目标(重试/回滚回到同一处)
- DocumentBundle.RemapKnowledgeBase:knowledges/chunks/knowledge_tags/wiki_pages/
  wiki_folders 的 knowledge_base_id 与 ES 文档字段改写为目标库,重算表/文档摘要并重新封装
- 契约比对改为「声明的映射对」(KnowledgeBaseMapping),其余字段继续 fail-closed
- 契约检查器 / 目标身份 / 验证器全部走映射后的目标库;对象通道无需改写
- Compose 拆分:数据面接线 compose.release-sim.yaml + 两道门禁 compose.release-gates.yaml
- 测试:RemapKnowledgeBase 全通道/无操作/fail-closed、映射契约、绑定接口、RBAC、真实 PG 集成
2026-10-08 15:41:13 +08:00
姚章浩 e1c5f6895a chore(deploy): 部署与备份脚本、示例配置与忽略规则
build / build (push) Failing after 21s
- 新增 scripts/deploy-api-web.sh(API/Web 构建发布)与 scripts/wiki_cleanup_backup.sh(wiki 清理前备份)
- 更新 .env.example、compose.yaml 与 README 说明;忽略 /spool/ingestion 运行期产物
2026-09-30 10:54:07 +08:00
姚章浩 e53b98f37d fix(alist): 网关鉴权头改为 X-Gateway-Token
- 依据 2026-09-28 实测:旧头对新网关 4/4 源返回 401,X-Gateway-Token 返回 200
- 取件通道 scripts/artifact_fetch.py 两处头名同步;镜像内生效需重建 worker
- 修正过期结论:下载直链 /p/<path>?sign=… 为签名鉴权,请求头不影响(无头亦 206)
- 同步文档与回归断言表述(重定向隔离测试仍 PASS)
2026-09-30 10:54:07 +08:00
姚章浩 ea6e56b304 feat(release): 文档发布会带上知识库 wiki 快照并支持撤权剥离
发布通道原来只有三条:PostgreSQL 关系行、Elasticsearch 分片、OSS 对象。
WeKnora 的 wiki 图谱是 wiki_pages 一张纯 PG 表(in_links/out_links 存 slug),
所以它不在任何通道里——撤权后生产 wiki 会一直留着已撤权文档的页面与引用。

本次新增第 5 条通道:库级 wiki 快照,挂在现有文档发布包里,检索与 wiki
同一个 release 单元(同一次执行、同一次校验、同一次回滚):

- 导出:ExportWiki 按 knowledge_base_id 全量读 wiki_pages/wiki_folders;
  收敛门禁(库内无解析中/合成中文档)不满足直接失败并给中文原因;
  未启用 wiki 且无 wiki 行的库不参与同步(生产 wiki 保持原样)。
- 撤权:item.Action == "delete" 时在导出端剥离该文档的 wiki 贡献
  (source_refs/chunk_refs 去条目、无引用页面整页不带、摘死链、清 parent_slug、
  无页面引用的空文件夹及其祖先一并去掉),使“源端仍在只停止发布”和
  “源端删除”两种情况收敛到同一结果。
- 导入:库级 replace-by-kb(同一事务内 DELETE by kb 后灌入快照,文件夹先于页面),
  新增 ReplaceWiki 供撤权路径使用(文档行只 disable,wiki 整体替换)。
- 校验:VerifyWiki 用生产 WeKnora wiki API 比对页面 slug 集合,撤权时额外断言
  生产 wiki 不再引用被撤权文档。

驱动与验收:新增 cmd/release-channel-e2e(复用发布 worker 真实代码路径,
支持 publish/withdraw/rollback),scripts/wiki_e2e_test5.sh、
scripts/wiki_e2e_newkb.sh 两套端到端脚本;证据见
docs/WIKI_CO_TRANSPORT_ACCEPTANCE.md。
2026-09-30 10:54:07 +08:00
姚章浩 822896ea6c fix(fetch): 钉钉空文档显式报 empty_document
DingTalk exports empty online documents to valid but contentless docx/xlsx;
WeKnora then rejects them with a generic remote_parse_failed. Detect blank
exports (no text, no embedded media) at fetch time and report the cause in
Chinese, and add the matching dead-letter hint.
2026-09-30 10:54:07 +08:00
姚章浩 257be88ecf fix(fetch): UTF-8 sniff boundary must not reject valid documents
validate_magic read exactly 4096 bytes and strict-decoded them; a multibyte
character straddling the boundary made perfectly valid .md/.txt files fail
with invalid_format. Only tolerate 'unexpected end of data' when the file
actually continues past the sniff window; genuinely corrupt bytes still fail.
2026-09-30 10:54:07 +08:00
姚章浩 b3c9331966 feat(ingest): 源与目录文件名过滤及钉钉文档导出
- filename_filter_rules table (000035) with source-root and per-directory rows
- additive inheritance: source default + every ancestor directory contributes
- filename_is_filtered() SQL predicate applied to catalog browsing, subtree
  counts, change-set diffing, routing fan-out and artifact fetch manifests
- fetch_state 'filtered' (CHECK extended); reconcile cancels/restores fetch
  jobs, retires planned publications and blocks items when rules change
- GET/PUT/DELETE /api/v1/filename-filters (Idempotency-Key, normalized
  extensions) + Settings and Catalog filter editors
- failJobSQL no longer swallows the claimed row when the failure projection
  upserts (fixes spurious 'lease lost' retries)
- DingTalk live fetch: adoc/axls now use the export-job pipeline (HTTP 403
  from file/download fixed), header-signature downloads supported
2026-09-30 10:54:07 +08:00
姚章浩 e60b10256a fix(settings): add DingTalk spaces and render the real login QR
Address two gaps in the first DingTalk-session UI:
- add an explicit admin-only '+ 新增钉钉知识库' action on the data-source
  panel; it accepts an alidocs space URL, resolves the live name/root through
  the authenticated sidecar, writes the inventory root atomically, upserts a
  deterministic source connection, and enqueues its first catalog refresh
- read DingTalk's real QR canvas pixels rather than taking a DOM screenshot;
  select by dark-pixel content, composite onto a white quiet zone, and render
  at 320x320 so the dialog contains a large complete code, not a page thumbnail

The live refresh path resolves registered roots from the connection locator,
while existing name-based DingTalk sources remain compatible.
2026-09-30 10:54:07 +08:00
姚章浩 c2d093bc90 fix(fetch): AList download route authenticates with x-api-token
This AList deployment (sales.dobotoa.cn family) only recognizes x-api-token on
the /p/ download route; an Authorization header gets 302'd to login.dingtalk.com.
Probe x-api-token first, fall back to Authorization, then fail-closed.
2026-09-30 10:54:07 +08:00
姚章浩 63f3b2c503 feat(governance): policy-driven ingestion from catalog annotations
Annotation in 来源与目录 is now the only import decision surface:
- migration 000033: catalog_import_intents (durable, rescan-proof per-file
  intent with locator snapshot + fetch lifecycle), source_items.policy_state,
  planned publication state, ui_setting_revisions.ingestion_concurrency
- marking fans a folder decision out to per-file intents, materializes
  versioned items, reuses already-verified local bytes (compare, don't pull),
  and plans one publication per routed KB; blocked retires planned rows
  while live publications stay untouched (需下架 shows in the KB diff)
- artifact.fetch worker lane (scripts/artifact_fetch.py): DingTalk export
  tree/live box API, AList /api/fs/get direct download with SSO-redirect
  fail-closed, PP ledger via locator hash; content-level dedupe on sha256
- 开始处理 promotes planned->pending inside the claim-time concurrency budget
  from system settings; claims enforce it in SQL
- monitor overview rebuilt as knowledge-base diff: planned/needs_removal per
  KB + fetch pipeline summary; frontend lanes 待入库/需下架 replace the
  deprecated snapshot change-set confirmation
- spool: images (png/jpg/gif/bmp/webp) accepted, ppt/pptx rejected in favour
  of PDF twins; DingTalk PPT resolves its converted PDF pair
2026-09-30 10:54:07 +08:00
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00