Files
dsh_ai1net_server/交付物/节点与端-互联互通与调用关系-20260926.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

12 KiB
Raw Permalink Blame History

节点与端 · 互联互通 · 数据 / 实例 / 三方插件的关系与调用(2026-09-26)

本文 = 关系与调用地图(成品·描述性)。能做/不能做 ⇒ 基础服务插件-能做不能做总清单与拍板路径-20260926.md;逐模块明细 ⇒ 平台新增功能总盘点与插件化归属-20260926.md。 ⚠️ 本文每条关系都带代码依据(表名 / 模块 / 端点),⛔ 不含推演结论;推演部分集中放 §7。


0 三句话判定

  1. 四种"端"、两类"网"、一条总入口 —— 所有互联都收敛到同一条链:先入网(拿身份)→ 再准入(网维度)→ 才有通路(隧道)。
  2. 「节点」在代码里有三个不同所指,混着说必出分歧 ⇒ 覆盖网络的注册表节点 / 承载体的 Worker / 判决者的 Manager。
  3. 数据 / 实例 / 三方插件是三层不同的东西,各有专属通路、⛔ 不共用:实例 = "跑在哪台机",数据 = "谁说了算",插件 = "装进哪个实例"。

1 全部节点与端(一张表打全)

种类 在册在哪 属于哪张网 身份凭据 备注
Manager(控制面 · 判决者) relay 的拨号方(dialer)白名单 ops relay 密钥文件 + 拨号方白名单 唯一权威;network_id 默认 ops
Worker(承载体 · 执行体) dsh_hosts 行(含 via 经谁可达 + network_id) ops worker agent 共享密钥头 无自己的库;只被拨入
中继 relay 自己的端点表 + 密钥文件 —(服务所有网) 对端密钥 两台(只绑回环)+ 443 兜底入口
服务器实例 overlay_devices(kind='instance') u:<userId> 设备凭据(写进实例 home) 🔴 实例在网里"是一台设备",不是会话
用户桌面设备 overlay_devices(kind='desktop') u:<userId> 设备凭据(密钥对在设备上生成,私钥永不出机) 24 h 租约,可续签、可停发
浏览器会话 sessions(kind='browser') —(不入网) sid cookie ⛔ 不入覆盖网络
桌面会话 sessions(kind='desktop') —(不入网) sid cookie 同上

网的两类取值(已定项):ops = 运维网(平台自己的机器:Manager / Worker / 中继 / 骨干);u:<userId> = 用户网(该用户名下全部设备,含桌面客户端)。 逻辑名 = <network_id>/<hostId>;network_id 不含 / ⇒ 首个 / 即分隔点(唯一入口,⛔ 调用方不许自己拼)。

🔴 「节点」的三个所指(本文全篇按此区分):① 覆盖网络注册表节点(网 × hostId,有 pending / approved)② Worker(承载体,dsh_hosts)③ Manager(判决者)。 ⚠️ 「当节点」这类说法必须点明是哪一个 —— 否则歧义直接从"能不能做"变成"做了两个不同的东西"。

依据:db/schema.ts v9(dsh_hosts.network_id 默认 ops)· v12(overlay_devices 复合主键 (network, host_id))· v13(kind 两处值域)|net/relay/network.ts(OPS_NETWORK / logicalName / NAME_SEP)|net/relay/registry.ts(注册表 + 准入)。


2 互联互通:三个必须过的门(⛔ 顺序不可换)

门 做什么 机制 不过会怎样
① 入网(拿身份) 冷启动先找到中继,再换一份通行的凭据 三级链:env > 缓存目录 > 内置种子;目录签名验签;凭据只含"哪张网+有效期+一次性 nonce",⛔ 不含密钥本体 失败关闭 —— 签名不对既不写缓存也不用它的地址
② 准入(网维度) 判断"这台机器在这张网里能不能拨" relay 白名单 Map<network, Set<hostId>>,默认拒绝 跨网在"能不能拨"这一步就走不到 ⇒ 结构性隔离,⛔ 不是靠 ACL 兜
③ 通路(隧道) 真正建连接 中继 443(功能全可用、⛔ 不用开任何端口);UDP 段只服务 P2P(可选优化) 连不上就没通路,⛔ 不静默降级到别的地址

签发一份设备凭据要过五道门(net/relay/device-grant.ts,⛔ 唯一实现): 停发闸门 → 用户配额 → 签 grant → 注册表落盘 → 台账 → relay 密钥表。 ⚠️ 密钥表写在最后是刻意的:密钥表一写,这台设备就真的能拨进来了;台账写在它之前,则"台账写失败"时还没有任何功能性后果。反过来排序会造出"查不到归属、却能拨进来"的设备。

依据:net/relay/directory.ts(三级链 + 三条判据)|net/relay/registry.ts(三条硬约束 + deriveDialers)|net/relay/network.ts(网维度是结构性的)|net/relay/device-grant.ts(五道门)。


3 谁能连谁(联通矩阵)

从 \ 到 控制面 Worker 实例 用户设备
控制面 — ✅ 拨入 Worker agent(单向) ✅ 文件/配置经 UserFs seam ⛔ 不主动连设备
Worker ⛔ 不反连(协议纪律) 同机 ✅ 本机(回环 + OS 级运行单元) —
实例 ✅ HTTP ⛔ ✅ 同网内(P2P 可选,443 中继兜底) ✅ 同网
用户设备 ✅ HTTPS(门户 / API) ⛔ ✅ 同网(双方都须在拨号方白名单) 同网

三条由此推出的硬事实:

  1. 🔴 Worker 单向拨入 —— Worker 不反向连 Manager、不写控制面数据(协议纪律第一条)⇒ 所以"承载机器"天然可替换、可水平增加。
  2. 🔴 实例内没有平台代码 ⇒ 实例与平台的唯二通路就是 HTTP(实例凭据或会话 + 插件自称头)。
  3. 🔴 用户之间(跨网)连不上 —— 这是设计,不是缺陷:网按用户切,u:A 与 u:B 之间在"能不能拨"那一步就走不到。⇒ 跨用户协作只能走平台(443),⛔ 不是设备直连。

依据:worker/agent.ts(四条协议纪律)|fs/user-fs.ts(UserFs seam 的归属钉法)|web/routes/plugin-data.ts("唯二通路就是 HTTP" 原文)|net/relay/network.ts(按用户切网)。


4 数据 / 实例 / 三方插件 —— 三层,各走各的通路

4.1 一句话说清三者关系

东西 它回答的问题 权威在哪 谁动它
实例 "跑在哪台机" 控制面(归属 + 租约 + 纪元) 由 Worker 在本机执行起停
数据 "谁说了算" 分三层(见 4.2) 各有唯一写者
三方插件 "装进哪个实例" 控制面(候选池 + 版本) 装配在引导层,⛔ 不由插件自己装

⇒ 三者不是包含关系,是三个正交的归属:一个实例可以装 N 个插件;一个插件可以装进 M 个实例(各一份副本);而每个插件的数据只有一份权威(在平台侧,按归属列切分)。

4.2 数据的三层(⛔ 混了就会"看得到读不到")

层 在哪 唯一写者 踩过的坑
平台权威库 Manager 只有 Manager 双写 = 脑裂
插件库(一插件一库) Manager 的 PG 插件经 HTTP 写 归属列由平台强制注入,插件覆盖不了
实例 home 里的文件 Worker 那台机上 平台必须走 UserFs seam ⛔ 直写 = 静默空操作(返回空串、不报错)

4.3 三方插件的四个面(每个面的"谁来做"都不同)

面 谁来做 机制
① 装配面(把插件放进实例) 引导层 共享只读包库(每机一份);装配协议 link:;⛔ 装配动作不在插件里
② 运行面(插件跑在哪) 用户实例内 插件与实例同进程域 ⇒ 这正是它能用"用户自配的模型凭据"的原因(那套凭据只写进实例 home)
③ 取数面(插件怎么读数据) 插件 → HTTP → 平台 GET/POST/PATCH/DELETE /api/im/data/<table>;三层授权(谁在调 → 以哪个插件身份 → 数据归属);契约失败一律 200 + {ok:false}
④ 回调面(平台/端侧怎么叫插件) 插件 SDK + 扩展点 + 反向回调 插件界面半落在官方已有槽位

取数面的三层授权(顺序不可换): ① 谁在调 —— 浏览器会话(用户本人)或实例凭据(实例所属用户); ② 以哪个插件的身份 —— 请求头 x-dsh-im-plugin-id;平台只信自己的记录(在候选池 + 声明合法 + 台账 state='ready')⇒ 未建库的插件取不到任何数; ③ 数据归属 —— 平台强制注入归属列(user_id / room_id),插件覆盖不了。

4.4 一次调用的完整链路(谁经谁)

用户在插件界面点一下
   └─ 插件(跑在用户实例内)
        └─ HTTP 到平台:实例凭据 + 插件自称头
             └─ 平台三层授权(谁 / 哪个插件 / 归属)
                  └─ 强制注入归属列 ⇒ 插件自己的库
                       └─ 返回(失败也 200 + {ok:false})

若这次调用要"碰到别的用户"
   └─ ⛔ 不经设备直连   └─ 只有一条路:走平台(443 中继)

依据:db/plugin-data/*(一插件一库 + 声明期校验)|web/routes/plugin-data.ts(三层授权 + 端点表 + 残余缺口)|supervisor/plugin-assembly.ts(装配与共享层)|worker/agent.ts(实例侧入口)|fs/user-fs.ts(home 归属)。


5 三条绕不过的(判据,⛔ 换形态也不变)

  1. 身份先于通路 —— 拿不到凭据就拨不进来;而"拿凭据"这件事只能发生在插件存在之前(⇒ 身份是信任链的起点)。
  2. 网维度是结构性的 —— 隔离靠"这张网里有没有你",⛔ 不是靠一张可写错的 ACL 清单。
  3. 实例内没有平台代码 ⇒ 实例与平台唯二通路是 HTTP;这既是约束,也是"为什么插件必须在实例内"(否则拿不到用户自配的凭据)。

6 已知缺口与未定(⛔ 不假装完备)

# 内容 状态
1 插件"自称身份":同一用户的实例里,插件 A 可自称插件 B,读到 B 的、同一用户维度的行 ⚠️ 真实缺口(越不出用户边界;补法 = 每插件令牌,本期以逐次审计兜底)
2 插件数据面已落地、⛔ 未部署(47 / 106 未投放) 待部署
3 节点"提供哪些具体服务" 未定 业务方向,待拍板
4 承载归属迁移(实例换机)机制 未设计 ⛔ 不立项(只有缺点)
5 桌面侧拉取器 + 共享包接 profile 装配 已交对接单(桌面线)
6 "轻节点当内容副本 / 设备当消费者" 卡在待拍板项(设备读包权限)
7 跨用户互联(u:A ↔ u:B 直连) ⛔ 现状结构性不通 ⇒ 跨用户协作只能走平台;是否要打通属权限面决策

7 待拍板(承接 基础服务插件-能做不能做总清单与拍板路径-20260926.md §7,⛔ 不复制正文)

  • P1 插件规划走不走(产品主路径)—— 决定后面全部
  • P2 设备要不要有「读平台插件包」的权(权限面)
  • P3 节点提供哪些具体服务(业务方向)
  • P4(本文新登记)跨用户要不要打通 —— 现状是结构性不通(各用户一张网),跨用户协作只能走平台;打通 = 扩大可见面,属权限面决策。⚠️ 本条排在 P1 之后:若 P1 走"只装官方宿主 + 插件",跨用户协作本来就该走平台,P4 可能自动消失。