临时-04 · Market KB 物化 D0-fork 执行 Plan
发布侧 fork 纯公开副本 · v1(执行版,待人类 review 后执行,本稿只读出文档)· 2026-06-26
配套正文:临时-04-market-KB物化-D0fork执行plan.md | 上一版 临时-03(共享+运行时隔离)已被本稿 supersede
一句话:知识库上架时,在发布侧 fork 一份只含公开文档的专用 dataset("公开副本"),所有安装者只读共享这一份。
从物理上把发布者私有内容隔在副本之外,从根消除"安装者读到发布者私有内容"的越权——
不需要运行时 chunk 隔离、不依赖那个 metadata_filter 字段 bug。代价是新增一块发布侧 fork 工程(建副本+复制文档+重索引,异步重活)+ 打破 market→knowledge 零依赖接线。
越权解法:从"私有泄露"到"物理不可见"
✗ 现状 / 共享活 dataset(会越权)
- market 对 knowledge/ragflow 零依赖:上架审核只翻 market 自有资产状态,从不 fork 任何 dataset
- 安装者绑定后指向发布者那一份活 dataset
- 发布者上架后仍可继续加私有文档(whole-KB 上架、KB 不冻结)
- 检索链对返回 chunk 无任何二次过滤:dataset 进检索即整库返回 → 安装者读到上架时不存在、发布者事后才加的私有内容
- 租户拦截器拦不住:两边 binding 指向同一个 RAGFlow dataset_id
✓ D0-fork(物理隔离)
- 上架时 fork 出只含上架时刻公开快照的副本 dataset
- 安装者只读副本,发布者私有/上架后新增文档物理不在副本内
- "整库返回"在这里是安全的——整库 = 纯公开
- 无需运行时 chunk 隔离、无需 metadata 过滤
- 副本仅 1 份,N 个安装者共享只读
与临时-03 的差异:临时-03 把越权理解为"安装者之间互读(多租户共享 dataset)",方向是"共享前补 chunk 级隔离";
D1 澄清坐实真越权是安装者读到发布者私有内容,故改走物理隔离(fork 公开副本)。
临时-03 的 U0 spike 证据("整库无 chunk 过滤")仍有效,本稿转作 U-verify 的反向基线(证"私有不泄露")。
物理隔离:发布侧 vs 安装侧
发布者域(owner = 发布者)
发布者活 dataset kb-{发布者kbId}-v{ver}
含公开文档 + 上架后新增的私有文档(持续变化)
⭐ 公开副本 dataset fork-asset-{assetId}-public
只含上架时刻的公开快照文档(fork 后不随活 dataset 变)
↑ U-fork:上架时逐文档复制内容 + 重索引(异步重活)
安装者域(owner = 各安装者)
installed_ref kb 行(每安装者一行)
kb_type=installed_ref · source_market_asset_id=assetId · license_snapshot_id=授权快照
ragflow_binding(去污染后)
kb_id=本地 installed_ref 主键(不再是 assetId)· ragflow_dataset_id=公开副本
检索 selectActiveDatasetByKbId → 公开副本
RAGFlow 整库返回 = 🟢 纯公开,发布者私有物理不可见
越权根除点:安装者 binding 指向的 dataset_id 是公开副本而非发布者活 dataset。
即使检索链"整库返回 + 无二次过滤"的事实不变,整库里也只有上架时刻的公开内容——发布者私有从源头不在这份 dataset 里。
四个实现单元(各一句话)
U-fork 全新·最重
上架事件触发 knowledge 异步 fork:建副本 dataset + 逐文档复制 + 重索引 + 幂等/重试/部分成功;whole-KB 公开集 = 上架时刻全部可检索文档。
→
U-materialize
绑定路径建 installed_ref kb 行 + binding 指向公开副本;kb_id 去污染(assetId→本地主键)+ 新 MarketAssetSourceApi 跨 BC 读带回副本 dataset + forkStatus + 幂等。
→
U-retrieve
物化后第四门返非空→命中;无运行时隔离(副本纯公开);授权快照沿用 ADR-020 VARCHAR。代码几乎不改。
→
U-verify
真 PG IT(fork→安装→检索命中)+ studio e2e + 发布者私有不泄露(复用 U0 反向基线)+ 召回/下架回滚。
相对临时-03:U-materialize/U-retrieve = 原 U2/U3 简化去隔离;U-fork 是全新核心工程;U0 spike 的 2 个 case 复用 作 U-verify 反向基线,不重写。
fork 时序(上架触发 + 事件驱动 + 异步,含失败/重试/幂等)
1
管理员审核
approvePublishRequest(含 markListed)
2
market 审核服务
资产/版本 listed(market 自有事实,同事务)
3
market 审核服务
落上架事件 listed{assetId, 发布者kbId, version}(同事务) · 不等 fork,审核到此提交,不被 fork 成败绑架
4
knowledge 消费者
异步消费上架事件 → forkPublicSnapshot(assetId, 发布者kbId, version)
5
knowledge fork
幂等检查(该 assetId+version 已 fork?命中跳过)→ createDataset(副本)
6
knowledge fork
对每个上架时刻公开文档:uploadDocuments(副本) + startParseDocuments(副本重索引)
7
knowledge fork
全成功 → forkStatus=ready + 记副本 datasetId(供 MarketAssetSourceApi 带回)
部分失败/502 → forkStatus=partial/failed + 标可重试(不删已建部分、不重复传已成功文档)
无现成 RAGFlow "copy dataset" API → fork 必须重走 createDataset + 逐文档 upload + startParse(重索引)。这是异步长任务重活,是 D0-fork 相对临时-03 最大的新增成本。
关键决策点(待人类拍板)
| 决策 | 选项 | 倾向 + 理由 |
| fork 时机 |
上架 markListed 时 fork / 首次安装懒 fork |
上架时 fork:版本快照语义干净(副本=上架那刻公开集)+ 安装侧轻(无异步等待)+ 浪费可控;懒 fork 破坏快照语义 + 首装延迟 + 并发抢锁。 |
接线 (打破零依赖) |
事件驱动 / market 调 knowledge facade 同步 |
事件驱动:审核不被 fork 成败绑架 + 契合 ADR-017 + 保 BC 边界(market 不反向依赖 knowledge);facade 同步会让审核挂在 RAGFlow 多次调用上、失败回滚审核。 |
| 副本更新 |
重新上架才更新 / 不更新 / 实时跟随 |
重新上架才更新:与"市场资产=发布快照"模型一致;实时跟随会重新引入私有泄露。 |
fork 未就绪 时安装 |
绑定阶段拒(fail-closed)/ 绑定但检索 no_dataset |
绑定阶段拒:避免"绑了用不了"迷惑态;实现时定夺回填。 |
召回/下架 回滚 |
复用现有传播 / 本期标开放项手动停用 |
现状 market→目标 owner 传播是 fail-closed blocked 记录式(TARGET_OWNER_UNAVAILABLE),knowledge 无消费入口 → 倾向本期标开放项,不假装闭环。 |
DDL / 副本存放 |
复用 JSONB 不加列 / 新增列或表(V32) |
复用现有结构 + JSONB 承载 fork 状态/副本映射 → 不需要新迁移;仅评审坚持强类型列时才走 V32(_test 库铁律)。 |
风险与边界(诚实呈现)
主要风险
- R1 私有泄露:fork 公开集界定错 / 副本"实时跟随"→私有重新泄露。缓解:严格按上架时刻 active 文档 + U-verify 真验私有不泄露。
- R2 fork 异步重活复杂度(不掩盖):建 dataset+N 文档逐个 upload+重索引,失败/重试/幂等/部分成功——比临时-03"共享+运行时隔离"重。诚实:安全模型更干净(物理隔离),但发布侧工程更重。
- R3 打破 market→knowledge 零依赖:影响面集中在"上架落事件"+knowledge fork 消费链,不改 market 审核主体;需架构 review(ADR-017 为据)。
- R6 metadata_filter 字段 bug 潜伏·留待修:发的 metadata_filter 被 RAGFlow 静默忽略(应为 metadata_condition)。D0-fork 不依赖它,降级潜伏 bug 单记。
本期不做(Scope)
- agent 物化(下一阶段,不碰 ai 模块)
- install 侧物化(维持只记账 targetFactsWritten=false)
- 运行时 chunk 隔离(D0-fork 用物理隔离替代)
- metadata_filter 字段 bug 修复(潜伏单记)
- 副本资源回收(下架/无安装清理副本,开放)
- 召回/下架回滚自动化(开放项,不假装闭环)
- market 两列 BIGINT 契约议题
DDL 判定:初版不需要新 Flyway 迁移(installed_ref 落 V5/V14 已有列 + fork 状态走 JSONB);仅评审坚持强类型列才走 V32。