D1 澄清坐实真越权=安装者读到发布者私有内容(非安装者间):market 对 knowledge/ragflow 零依赖(src+pom 双空 spot-check)→上架不 fork、安装者共享发布者活 dataset、上架后 KB 仍可加私有文档(上传无 market 门禁)。用户拍 D0-fork(发布侧 fork 纯公开副本、物理隔离)。 临时-04 四单元:U-fork(发布侧 fork 公开副本核心新工程:上架事件→knowledge 异步 createDataset+逐文档 uploadDocuments+重索引,RAGFlow 无 copy API 必重走三步 spot-check 确认)/U-materialize(安装侧物化去隔离、binding 指公开副本)/U-retrieve(检索去隔离)/U-verify(发布者私有不泄露、复用临时-03 U0 反向基线)。关键推荐:fork 时机=上架 markListed(副本=公开快照语义)、接线=事件驱动(复用 market EventPublishOutbox+ADR-017、不绑架审核事务、保 market 不反依赖 knowledge)、DDL=初版不需(V5/V14 列+JSONB)。临时-03 标 superseded 保留演进史、U0 证据+四硬事实继承。待拍 D4-D10 后执行。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
292 lines
19 KiB
HTML
292 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>临时-04 · Market KB 物化 D0-fork 执行 Plan(人读图)</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0f1419; --panel:#1a2230; --ink:#e6edf3; --muted:#9fb0c3;
|
||
--pub:#c0392b; --pubbg:#2a1a1c; --ins:#27ae60; --insbg:#16271c;
|
||
--fork:#2ecc71; --warn:#b8860b; --warnbg:#2a2410; --line:#33415a;
|
||
--accent:#4aa3ff; --accentbg:#13243a;
|
||
}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg);color:var(--ink);
|
||
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
||
line-height:1.6;padding:32px 20px 64px}
|
||
.wrap{max-width:1180px;margin:0 auto}
|
||
h1{font-size:26px;margin:0 0 4px;letter-spacing:.3px}
|
||
.sub{color:var(--muted);font-size:14px;margin-bottom:6px}
|
||
.oneliner{background:var(--accentbg);border:1px solid var(--accent);border-radius:10px;
|
||
padding:14px 18px;margin:18px 0 26px;font-size:15px}
|
||
.oneliner b{color:var(--accent)}
|
||
h2{font-size:18px;margin:34px 0 14px;padding-left:10px;border-left:4px solid var(--accent)}
|
||
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px 20px;margin-bottom:18px}
|
||
/* 物理隔离主图 */
|
||
.iso{display:grid;grid-template-columns:1fr 56px 1fr;gap:0;align-items:stretch;margin:8px 0}
|
||
.col{border-radius:12px;padding:16px}
|
||
.col.pub{background:var(--pubbg);border:1px solid var(--pub)}
|
||
.col.ins{background:var(--insbg);border:1px solid var(--ins)}
|
||
.col h3{margin:0 0 12px;font-size:15px}
|
||
.col.pub h3{color:#ff8a7a}
|
||
.col.ins h3{color:#6fe0a0}
|
||
.box{background:#0d141d;border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:8px 0;font-size:13px}
|
||
.box .t{font-weight:600;margin-bottom:3px}
|
||
.box .d{color:var(--muted);font-size:12px}
|
||
.box.live{border-color:var(--pub)}
|
||
.box.fork{border-color:var(--fork);box-shadow:0 0 0 1px rgba(46,204,113,.25) inset}
|
||
.box.fork .t{color:#6fe0a0}
|
||
.arrowcol{display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative}
|
||
.bigarrow{font-size:30px;color:var(--fork);font-weight:700}
|
||
.arrowlbl{font-size:11px;color:var(--muted);text-align:center;margin-top:4px;max-width:54px}
|
||
.copyhint{font-size:11px;color:var(--fork);text-align:center;margin-top:8px;border-top:1px dashed var(--fork);padding-top:8px}
|
||
.verdict{margin-top:14px;background:var(--insbg);border:1px solid var(--ins);border-radius:8px;padding:12px 14px;font-size:13.5px}
|
||
.verdict b{color:#6fe0a0}
|
||
/* 越权 before/after */
|
||
.ba{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
||
.ba .bad{background:var(--pubbg);border:1px solid var(--pub);border-radius:10px;padding:14px}
|
||
.ba .good{background:var(--insbg);border:1px solid var(--ins);border-radius:10px;padding:14px}
|
||
.ba h4{margin:0 0 8px;font-size:14px}
|
||
.ba .bad h4{color:#ff8a7a}
|
||
.ba .good h4{color:#6fe0a0}
|
||
.ba ul{margin:6px 0 0;padding-left:18px;font-size:13px;color:var(--muted)}
|
||
.ba li{margin:4px 0}
|
||
.tag{display:inline-block;font-size:11px;padding:1px 7px;border-radius:10px;margin-left:6px;vertical-align:middle}
|
||
.tag.new{background:#3a1418;color:#ff8a7a;border:1px solid var(--pub)}
|
||
.tag.reuse{background:#16271c;color:#6fe0a0;border:1px solid var(--ins)}
|
||
/* 实现单元流 */
|
||
.units{display:flex;gap:0;align-items:stretch;flex-wrap:wrap}
|
||
.unit{flex:1;min-width:200px;background:#0d141d;border:1px solid var(--line);border-radius:10px;padding:14px;position:relative}
|
||
.unit.core{border-color:var(--pub);background:var(--pubbg)}
|
||
.unit h4{margin:0 0 6px;font-size:14px}
|
||
.unit.core h4{color:#ff8a7a}
|
||
.unit .one{font-size:12.5px;color:var(--muted)}
|
||
.ufarrow{display:flex;align-items:center;font-size:22px;color:var(--accent);padding:0 6px}
|
||
/* 时序简版 */
|
||
.seq{font-size:13px}
|
||
.seq .step{display:flex;gap:10px;margin:7px 0;align-items:flex-start}
|
||
.seq .n{flex:none;width:22px;height:22px;border-radius:50%;background:var(--accentbg);border:1px solid var(--accent);
|
||
color:var(--accent);font-size:12px;display:flex;align-items:center;justify-content:center;font-weight:700;margin-top:1px}
|
||
.seq .who{flex:none;width:130px;color:var(--muted);font-size:12px}
|
||
.seq .what{flex:1}
|
||
.seq .async{color:var(--warn);font-size:11px}
|
||
/* 决策表 */
|
||
table{width:100%;border-collapse:collapse;font-size:13px;margin-top:6px}
|
||
th,td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
|
||
th{background:#0d141d;color:var(--muted);font-weight:600}
|
||
.rec{color:#6fe0a0;font-weight:600}
|
||
/* 通用 */
|
||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
||
.note{font-size:12px;color:var(--muted);margin-top:6px}
|
||
.pill{display:inline-block;background:var(--warnbg);border:1px solid var(--warn);color:#e0c060;
|
||
font-size:11px;padding:1px 8px;border-radius:10px}
|
||
.foot{color:var(--muted);font-size:12px;margin-top:30px;border-top:1px solid var(--line);padding-top:14px}
|
||
@media (max-width:820px){.iso{grid-template-columns:1fr}.arrowcol{flex-direction:row;padding:8px 0}
|
||
.ba,.grid2{grid-template-columns:1fr}.units{flex-direction:column}.ufarrow{transform:rotate(90deg);padding:6px 0}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>临时-04 · Market KB 物化 <span style="color:var(--fork)">D0-fork</span> 执行 Plan</h1>
|
||
<div class="sub">发布侧 fork 纯公开副本 · v1(执行版,待人类 review 后执行,本稿只读出文档)· 2026-06-26</div>
|
||
<div class="sub">配套正文:<code>临时-04-market-KB物化-D0fork执行plan.md</code> | 上一版 <code>临时-03</code>(共享+运行时隔离)已被本稿 supersede</div>
|
||
|
||
<div class="oneliner">
|
||
<b>一句话:</b>知识库上架时,在<b>发布侧</b> fork 一份<b>只含公开文档的专用 dataset</b>("公开副本"),所有安装者只读共享这一份。
|
||
从<b>物理上</b>把发布者私有内容隔在副本之外,从根消除"安装者读到发布者私有内容"的越权——
|
||
<b>不需要运行时 chunk 隔离、不依赖那个 metadata_filter 字段 bug</b>。代价是新增一块发布侧 fork 工程(建副本+复制文档+重索引,异步重活)+ 打破 market→knowledge 零依赖接线。
|
||
</div>
|
||
|
||
<!-- ============ 越权真因 before/after ============ -->
|
||
<h2>越权解法:从"私有泄露"到"物理不可见"</h2>
|
||
<div class="card">
|
||
<div class="ba">
|
||
<div class="bad">
|
||
<h4>✗ 现状 / 共享活 dataset(会越权)</h4>
|
||
<ul>
|
||
<li>market 对 knowledge/ragflow <b>零依赖</b>:上架审核只翻 market 自有资产状态,<b>从不 fork</b> 任何 dataset</li>
|
||
<li>安装者绑定后指向<b>发布者那一份活 dataset</b></li>
|
||
<li>发布者<b>上架后仍可继续加私有文档</b>(whole-KB 上架、KB 不冻结)</li>
|
||
<li>检索链对返回 chunk <b>无任何二次过滤</b>:dataset 进检索即<b>整库返回</b> → 安装者读到上架时不存在、发布者事后才加的私有内容</li>
|
||
<li class="note">租户拦截器拦不住:两边 binding 指向同一个 RAGFlow dataset_id</li>
|
||
</ul>
|
||
</div>
|
||
<div class="good">
|
||
<h4>✓ D0-fork(物理隔离)</h4>
|
||
<ul>
|
||
<li>上架时 fork 出<b>只含上架时刻公开快照</b>的副本 dataset</li>
|
||
<li>安装者只读<b>副本</b>,发布者私有/上架后新增文档<b>物理不在副本内</b></li>
|
||
<li>"整库返回"在这里<b>是安全的</b>——整库 = 纯公开</li>
|
||
<li>无需运行时 chunk 隔离、无需 metadata 过滤</li>
|
||
<li class="note">副本仅 1 份,N 个安装者共享只读</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="note" style="margin-top:12px">
|
||
与临时-03 的差异:临时-03 把越权理解为"安装者<b>之间</b>互读(多租户共享 dataset)",方向是"共享前补 chunk 级隔离";
|
||
D1 澄清坐实真越权是<b>安装者读到发布者私有内容</b>,故改走物理隔离(fork 公开副本)。
|
||
临时-03 的 U0 spike 证据("整库无 chunk 过滤")仍有效,本稿转作 U-verify 的反向基线(证"私有不泄露")。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ 物理隔离主图 ============ -->
|
||
<h2>物理隔离:发布侧 vs 安装侧</h2>
|
||
<div class="card">
|
||
<div class="iso">
|
||
<div class="col pub">
|
||
<h3>发布者域(owner = 发布者)</h3>
|
||
<div class="box live">
|
||
<div class="t">发布者活 dataset <code>kb-{发布者kbId}-v{ver}</code></div>
|
||
<div class="d">含公开文档 + 上架后新增的私有文档(持续变化)</div>
|
||
</div>
|
||
<div class="box fork">
|
||
<div class="t">⭐ 公开副本 dataset <code>fork-asset-{assetId}-public</code></div>
|
||
<div class="d">只含<b>上架时刻</b>的公开快照文档(fork 后不随活 dataset 变)</div>
|
||
</div>
|
||
<div class="copyhint">↑ U-fork:上架时逐文档复制内容 + 重索引(异步重活)</div>
|
||
</div>
|
||
|
||
<div class="arrowcol">
|
||
<div class="bigarrow">⇒</div>
|
||
<div class="arrowlbl">N 安装者<br>共享只读<br>副本仅 1 份</div>
|
||
</div>
|
||
|
||
<div class="col ins">
|
||
<h3>安装者域(owner = 各安装者)</h3>
|
||
<div class="box">
|
||
<div class="t">installed_ref kb 行(每安装者一行)</div>
|
||
<div class="d">kb_type=installed_ref · source_market_asset_id=assetId · license_snapshot_id=授权快照</div>
|
||
</div>
|
||
<div class="box">
|
||
<div class="t">ragflow_binding(去污染后)</div>
|
||
<div class="d">kb_id=<b>本地 installed_ref 主键</b>(不再是 assetId)· ragflow_dataset_id=<b>公开副本</b></div>
|
||
</div>
|
||
<div class="box" style="border-color:var(--ins)">
|
||
<div class="t" style="color:#6fe0a0">检索 selectActiveDatasetByKbId → 公开副本</div>
|
||
<div class="d">RAGFlow 整库返回 = 🟢 纯公开,发布者私有物理不可见</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="verdict">
|
||
<b>越权根除点:</b>安装者 binding 指向的 dataset_id 是<b>公开副本</b>而非发布者活 dataset。
|
||
即使检索链"整库返回 + 无二次过滤"的事实不变,整库里也只有上架时刻的公开内容——发布者私有从源头不在这份 dataset 里。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ 四个实现单元 ============ -->
|
||
<h2>四个实现单元(各一句话)</h2>
|
||
<div class="card">
|
||
<div class="units">
|
||
<div class="unit core">
|
||
<h4>U-fork <span class="tag new">全新·最重</span></h4>
|
||
<div class="one">上架事件触发 knowledge 异步 fork:建副本 dataset + 逐文档复制 + 重索引 + 幂等/重试/部分成功;whole-KB 公开集 = 上架时刻全部可检索文档。</div>
|
||
</div>
|
||
<div class="ufarrow">→</div>
|
||
<div class="unit">
|
||
<h4>U-materialize</h4>
|
||
<div class="one">绑定路径建 installed_ref kb 行 + binding 指向<b>公开副本</b>;kb_id 去污染(assetId→本地主键)+ 新 <code>MarketAssetSourceApi</code> 跨 BC 读带回副本 dataset + forkStatus + 幂等。</div>
|
||
</div>
|
||
<div class="ufarrow">→</div>
|
||
<div class="unit">
|
||
<h4>U-retrieve</h4>
|
||
<div class="one">物化后第四门返非空→命中;<b>无运行时隔离</b>(副本纯公开);授权快照沿用 ADR-020 VARCHAR。代码几乎不改。</div>
|
||
</div>
|
||
<div class="ufarrow">→</div>
|
||
<div class="unit">
|
||
<h4>U-verify</h4>
|
||
<div class="one">真 PG IT(fork→安装→检索命中)+ studio e2e + <b>发布者私有不泄露</b>(复用 U0 反向基线)+ 召回/下架回滚。</div>
|
||
</div>
|
||
</div>
|
||
<div class="note" style="margin-top:12px">
|
||
相对临时-03:U-materialize/U-retrieve = 原 U2/U3 <b>简化去隔离</b>;U-fork 是<b>全新</b>核心工程;U0 spike 的 2 个 case <span class="tag reuse">复用</span> 作 U-verify 反向基线,不重写。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ fork 时序 ============ -->
|
||
<h2>fork 时序(上架触发 + 事件驱动 + 异步,含失败/重试/幂等)</h2>
|
||
<div class="card seq">
|
||
<div class="step"><div class="n">1</div><div class="who">管理员审核</div><div class="what">approvePublishRequest(含 markListed)</div></div>
|
||
<div class="step"><div class="n">2</div><div class="who">market 审核服务</div><div class="what">资产/版本 listed(market 自有事实,同事务)</div></div>
|
||
<div class="step"><div class="n">3</div><div class="who">market 审核服务</div><div class="what">落<b>上架事件</b> listed{assetId, 发布者kbId, version}(同事务)<span class="async"> · 不等 fork,审核到此提交,不被 fork 成败绑架</span></div></div>
|
||
<div class="step"><div class="n">4</div><div class="who">knowledge 消费者</div><div class="what"><span class="async">异步</span>消费上架事件 → forkPublicSnapshot(assetId, 发布者kbId, version)</div></div>
|
||
<div class="step"><div class="n">5</div><div class="who">knowledge fork</div><div class="what">幂等检查(该 assetId+version 已 fork?命中跳过)→ createDataset(副本)</div></div>
|
||
<div class="step"><div class="n">6</div><div class="who">knowledge fork</div><div class="what">对每个上架时刻公开文档:uploadDocuments(副本) + startParseDocuments(副本重索引)</div></div>
|
||
<div class="step"><div class="n">7</div><div class="who">knowledge fork</div><div class="what">全成功 → forkStatus=ready + 记副本 datasetId(供 MarketAssetSourceApi 带回)<br>部分失败/502 → forkStatus=partial/failed + 标可重试(不删已建部分、不重复传已成功文档)</div></div>
|
||
<div class="note" style="margin-top:10px">无现成 RAGFlow "copy dataset" API → fork 必须重走 createDataset + 逐文档 upload + startParse(重索引)。这是异步长任务重活,是 D0-fork 相对临时-03 最大的新增成本。</div>
|
||
</div>
|
||
|
||
<!-- ============ 决策点 ============ -->
|
||
<h2>关键决策点(待人类拍板)</h2>
|
||
<div class="card">
|
||
<table>
|
||
<thead><tr><th style="width:120px">决策</th><th>选项</th><th style="width:300px">倾向 + 理由</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><b>fork 时机</b></td>
|
||
<td>上架 markListed 时 fork / 首次安装懒 fork</td>
|
||
<td><span class="rec">上架时 fork</span>:版本快照语义干净(副本=上架那刻公开集)+ 安装侧轻(无异步等待)+ 浪费可控;懒 fork 破坏快照语义 + 首装延迟 + 并发抢锁。</td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>接线</b><br>(打破零依赖)</td>
|
||
<td>事件驱动 / market 调 knowledge facade 同步</td>
|
||
<td><span class="rec">事件驱动</span>:审核不被 fork 成败绑架 + 契合 ADR-017 + 保 BC 边界(market 不反向依赖 knowledge);facade 同步会让审核挂在 RAGFlow 多次调用上、失败回滚审核。</td>
|
||
</tr>
|
||
<tr>
|
||
<td>副本更新</td>
|
||
<td>重新上架才更新 / 不更新 / 实时跟随</td>
|
||
<td><span class="rec">重新上架才更新</span>:与"市场资产=发布快照"模型一致;实时跟随会重新引入私有泄露。</td>
|
||
</tr>
|
||
<tr>
|
||
<td>fork 未就绪<br>时安装</td>
|
||
<td>绑定阶段拒(fail-closed)/ 绑定但检索 no_dataset</td>
|
||
<td><span class="rec">绑定阶段拒</span>:避免"绑了用不了"迷惑态;实现时定夺回填。</td>
|
||
</tr>
|
||
<tr>
|
||
<td>召回/下架<br>回滚</td>
|
||
<td>复用现有传播 / 本期标开放项手动停用</td>
|
||
<td>现状 market→目标 owner 传播是 fail-closed blocked 记录式(TARGET_OWNER_UNAVAILABLE),knowledge 无消费入口 → <span class="rec">倾向本期标开放项</span>,不假装闭环。</td>
|
||
</tr>
|
||
<tr>
|
||
<td>DDL /<br>副本存放</td>
|
||
<td>复用 JSONB 不加列 / 新增列或表(V32)</td>
|
||
<td><span class="rec">复用现有结构 + JSONB 承载</span> fork 状态/副本映射 → <b>不需要新迁移</b>;仅评审坚持强类型列时才走 V32(_test 库铁律)。</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ============ 风险 + scope ============ -->
|
||
<h2>风险与边界(诚实呈现)</h2>
|
||
<div class="grid2">
|
||
<div class="card">
|
||
<h4 style="margin-top:0;color:#ff8a7a">主要风险</h4>
|
||
<ul style="font-size:13px;color:var(--muted);padding-left:18px;margin:6px 0">
|
||
<li><b style="color:var(--ink)">R1 私有泄露</b>:fork 公开集界定错 / 副本"实时跟随"→私有重新泄露。缓解:严格按上架时刻 active 文档 + U-verify 真验私有不泄露。</li>
|
||
<li><b style="color:var(--ink)">R2 fork 异步重活复杂度</b>(不掩盖):建 dataset+N 文档逐个 upload+重索引,失败/重试/幂等/部分成功——<b>比临时-03"共享+运行时隔离"重</b>。诚实:安全模型更干净(物理隔离),但发布侧工程更重。</li>
|
||
<li><b style="color:var(--ink)">R3 打破 market→knowledge 零依赖</b>:影响面集中在"上架落事件"+knowledge fork 消费链,不改 market 审核主体;需架构 review(ADR-017 为据)。</li>
|
||
<li><b style="color:var(--ink)">R6 metadata_filter 字段 bug</b> <span class="pill">潜伏·留待修</span>:发的 metadata_filter 被 RAGFlow 静默忽略(应为 metadata_condition)。D0-fork 不依赖它,降级潜伏 bug 单记。</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card">
|
||
<h4 style="margin-top:0;color:#6fe0a0">本期不做(Scope)</h4>
|
||
<ul style="font-size:13px;color:var(--muted);padding-left:18px;margin:6px 0">
|
||
<li>agent 物化(下一阶段,不碰 ai 模块)</li>
|
||
<li>install 侧物化(维持只记账 targetFactsWritten=false)</li>
|
||
<li>运行时 chunk 隔离(D0-fork 用物理隔离替代)</li>
|
||
<li>metadata_filter 字段 bug 修复(潜伏单记)</li>
|
||
<li>副本资源回收(下架/无安装清理副本,开放)</li>
|
||
<li>召回/下架回滚自动化(开放项,不假装闭环)</li>
|
||
<li>market 两列 BIGINT 契约议题</li>
|
||
</ul>
|
||
<div class="note">DDL 判定:<b>初版不需要新 Flyway 迁移</b>(installed_ref 落 V5/V14 已有列 + fork 状态走 JSONB);仅评审坚持强类型列才走 V32。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="foot">
|
||
owner 边界引 临时-02 / 临时-03 / 架构-02(§6 市场资产·§11.2 绑定授权·授权快照)/ 专题-03(§5 检索合同)/ ADR-017(Source 传播事件驱动)/ ADR-020(授权快照字符串承载)/ .agents/rules/bc-boundaries.md,本稿不重定义概念。
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|