桥基- L2 命名对象引擎( F1 CORE)
关键石. 它组成了四个经过证明的 独立制造的包 底盘: 命名、持久协调对象 双侧 连接 ——"活在两边的名队". 没错 克劳德沃克 - 不可知论它对经纪人一无所知 引擎是一个更大的系统 驱动器.
A 类 Substrate 位于一个认证连接的两侧, 拥有 名称
对象 (F1级: session + 键 queue),并说 底部动词 超过
铁丝网 -- -- 采用每个物体严格的FIFO测序,两个识别级,a
耐用发包箱 + dedupe 完全对接, 以及无效/ 授权决赛
法律规定.
bun install # links the four packages via workspace:* (run at the monorepo root)
bun test # 19 tests, all green (254 across the workspace)
# two-node CLI demo (see examples/README.md):
bun examples/client.ts --name C1 --port 7801
bun examples/client.ts --name C2 --port 7802 --connect 7801
有个可跑可走的车手 双节点 CLI
(单位:千美元)open 编号 : send 编号 : req 编号 : close 编号 : disconnect 编号 : connect以图像化
对象的生命周期,两个相克级别, 并完全通过断开.
它的四件包裹
| 软件包 | 在基底的作用 |
|---|---|
bun-reliable-ws | l1载体: 二进制接线、握手、重接、WS交货包、, AuthContext 满分 Negotiated 倾斜+ connId + 键 transcriptHash |
envelope-codec | 框架编码器( r)cborCodec: {env, body} - > CBOR 字节,不透明的车体与二进制 零基数64 |
mutual-key-auth | 页:1 AuthProvider (单位:千美元)asAuthProvider:相互挑战/回应、角色绝对、跨层降级约束 -- -- 现在 无克 (见出处 |
bun-mq | 持久对象 发件箱 +该 除尘器分类账 (收押金) ack, (中文(简体) ), StaleReceiptError, 7天的调试窗口) |
接线
SUBSTRATE A (dialer) SUBSTRATE B (listener)
───────────────────── ───────────────────────
app: open / req / send / close open? -> accept/reject; req -> res
│ ▲ ▲ │
▼ │ answer (accept/res/end/err) end-to-end, keyed to id │ ▼
┌──────────┐ per-object durable outbox dedupe ledger ┌──────────┐
│ Outbox │ (bun-mq, one seq authority) (bun-mq (peer,id) >= 7d) │ Sequencer│
└──────────┘ at-most-one-un-acked-in-flight in-seq delivery ────└──────────┘
│ ▲ ▲ │
│ │ ◀──────────── ack (hop delivery-ack, receipt-fenced) ─┘ │
▼ │ ──────────── open/send/req/res/accept/reject/close ──────▶│
Link: bun-reliable-ws ═══ ws (binary), may drop + reconnect ═══
Codec: envelope-codec frame = { env{v,kind,obj,objKind,id,seq,receipt}, body } -> CBOR
Auth: mutual-key-auth challenge/response, bound to connId + full cross-layer tuple
与流量相同的堆栈 + 组成 :
flowchart LR
subgraph A["Substrate A · dialer"]
AApp["app: open / req / send / close"]
AOut["Outbox<br/>durable bun-mq · 1 seq authority<br/>at-most-1-unacked-in-flight"]
ASeq["Sequencer<br/>in-seq · gap buffer · resync"]
AApp --> AOut
ASeq --> AApp
end
subgraph B["Substrate B · listener"]
BApp["app: accept/reject · req→res"]
BOut["Outbox"]
BDed["Dedupe ledger<br/>(peer,id) ≥ 7d"]
BSeq["Sequencer"]
BApp --> BOut
BDed --> BSeq
BSeq --> BApp
end
AOut ==>|"open/send/req/res/accept/reject/close"| BDed
BOut -.->|"ack · hop delivery-ack · receipt-fenced"| AOut
BOut ==>|"answer · accept/res/end/err · keyed to id"| ASeq
AOut <--> L
BOut <--> L
L["LINK<br/>bun-reliable-ws — binary ws, drop+reconnect<br/>envelope-codec — {env,body} → CBOR, zero base64<br/>mutual-key-auth — bound to connId + full cross-layer tuple"]
储存可插接(在耐久状态生活的地方)
每个对等端的发件箱,收件箱,去dupe分类账和对象注册都进入一个
Store (bun-mq的坚持合同). 默认不需要配置; 主机
想要状态在 ITS OWN 数据库中插入一个商店:
| 选项 | 国家生活的地方 |
|---|---|
| 无 | 内存, 不持久 |
dbPath /接受者 dbPathFor(peerId) | a SQLite 文件下方打开并关闭 |
storage /接受者 storageFor(peerId) | (单位:千美元) Store 所建主机; 底盘永远不会关闭 |
接受方优先发言: storageFor,则 storage,则 dbPathFor,则
dbPath。每个耐用密钥都用对等码来显示,所以 One store 可以服务每个
鼠标。 在主机数据库中保留桥状态,在主机表格旁边
在主机交易中:
import { createPeerAcceptor, sqliteStore } from "@claudewerk/bridge-substrate";
const bridgeStore = sqliteStore(appDb, { tablePrefix: "bridge_" }); // appDb opened { strict: true }
const peers = createPeerAcceptor({ peerId: "broker", auth, authorize, storageFor: () => bridgeStore });
A 类 queue 接受对象 acceptQueue() 然后将其耐用收件箱保存在
主机数据库:主机可以数出,检查和像管理
其余数据。 离开对等者留下一行供主机保留
或删除.
两个条目: Bun和浏览器
@claudewerk/bridge-substrate 是Bun入口(货车工厂,SQLite).
@claudewerk/bridge-substrate/client 是浏览器条目: 同一引擎
(单位:千美元)SubstrateCore,并导出为 Substrate),物体表面,发包箱,
接受路径和去dupe,没有 bun:sqlite,没有载体和没有 node:*编辑
阿宝 Substrate 是同一类加 dbPath; 没有任何第二份副本
逻辑. bun build --target=browser src/client.ts 是一个考验.
同步持久存储配置文件( 中继 + 浏览器)
对于只能是同步的商店(RFC0005§8.3). 同步 上面的剖面图没有变化;这是加上两种表格.
中继形式:溪流前的无国籍边缘 (RFC 0006 第9.1.1节,第7.5节). 边缘没有持久状态;任何复制品都可以为任何会话服务.
const edge = new SubstrateCore(link, {
authorize, peerId: "edge", remotePeerId: browserId,
relay: {
sink: jetStreamSink(js, "GATE_IN"), // store(op) -> Promise<{seq, duplicate?}>, idempotent by (peer, id)
feed: jetStreamFeed(js, "GATE_EVENTS"), // resume(target, fromSeq): push the object's stream from fromSeq
},
});
- 入境: 每一个接受的操作都前往
sink.storea. 每物体序列链; 页:1ack("已存")只在解后发去. 失败的商店发送不 ack并放弃了背后的操作;浏览器重新发送. 重播失败 在水槽旁边,然后重新打包。 从浏览器中采纳对象和边框. - 出道:每个对象的浏览器所带方向是流. 框架带
之后的溪流和
prev(前一个框架的后几个),所以一个丢失的框架被告知 除间隙外; 浏览器恢复resync{fromSeq}在任何复制品. examples/jetstream-adapter.ts是 JetStream 的无依赖形状 适配器;memorySink编号 :memoryFeed(src/memory-stream.ts)为假相.
日记窗体: IndexedDB 上的浏览器发送框 (RFC 0006 第8.1.1.1节).
import { createClientSubstrate, openIdbJournal } from "@claudewerk/bridge-substrate/client";
const journal = await openIdbJournal({ name: `bridge:${me}:${edgeId}` });
const s = await createClientSubstrate(link, { authorize, peerId: me, remotePeerId: edgeId, journal, resumeFeeds: true });
每个操作都写入索引数据库( 等待, 一个严格可持久交易)
在它到达铁丝网之前, 释放出它的跳起, 并在重新装入后怨恨。 A 类
失败的写入( quota) 拒绝 send。此处不支持 : acceptQueue。 。 。 .
恰好通过强制断开(资金流动)
sequenceDiagram
participant A as Substrate A (Outbox)
participant Wire as ws link
participant B as Substrate B (Dedupe→app)
A->>A: enqueue send(id=M-1, seq=n) — durable, BEFORE the wire
A->>Wire: send{id:M-1, receipt:r1}
Wire->>B: send{id:M-1, receipt:r1}
B->>B: admit(peer,M-1)=fresh → commit, deliver to app once
Note over Wire: 💥 connection drops — ack lost
B--xWire: ack{M-1, r1} (never arrives)
Wire-->>A: reconnect (new connId, same peer → rebind)
A->>Wire: RESEND send{id:M-1, receipt:r1} (same producer-stable id)
Wire->>B: send{id:M-1}
B->>B: admit(peer,M-1)=DUP → skip app (dedupe wins)
B->>Wire: ack{M-1, r1} (branchless re-ack)
Wire->>A: ack{M-1, r1}
A->>A: q.ack(r1) receipt-fenced → outbox row freed
Note over A,B: delivered exactly once · FIFO intact
对象模型
一个 对象 是双方都存在的指定协调终点:
- 地址 - 一个不透明的符号 底部MATCHES,从不分析.
字符集
[a-z0-9._/-]{1,128}; 一个@实值routing_not_supported。 。 。 .objKind(单位:千美元)session|queue)是一个明确的信封字段,而不是从名称中解析. - 生命周期 - 说吧
open- > (中文)accept编号 :reject,然后活着,然后close(或 a 个) 终端err) (中文(简体) ). 两个终点跟踪对象的 与会者; 杀人 动词仅来自经核实的参与者. - 会议 -- 支持
req编号 :res(端对接)和单向send。 。 。 . - 队列 - 同一机器,面向发送(单向)
send+ 键ack) (中文(简体) ).
动词( O)env.kind(中文(简体) )
| 动词 | 方向 | 含义 |
|---|---|---|
open 编号 : accept 编号 : reject | 打开器qq接受者 | 建立或拒绝对象 |
close | 任何参与者 | 终止对象( 授权终局) |
send + 键 ack | 生产商 - > 端点 | 单向信息; ack 是跳跃快递包 |
req 编号 : res | 呼叫者 qq 呼叫者 | 请求 + 端对端答案 |
err | 调用对象/ 底盘 | 终端错误( 接收器从代码中得出的类) |
end | 被调用者 | 终端答尾 |
chunk 编号 : resync | - 说吧 | chunk 是一个F2接合; resync 内部控制接缝 |
两种承认水平
- 跳接快递 (单位:千美元)
ack, QoS-1, 这个连接). 由接收器发送 行动组被长期投入到破烂分类账中 它释放了发送者 发件箱行, 收受额 通过回声env.receipt输了 连接 - > 发送者在重新连接上重传同一框架 - > 接收器 脱发 重新交付和 重装。这就是交付 以及严格FIFO的强制措施(在飞行中,大多数行动都是未撤销的) 每个物体). - 端对端答案 (单位:千美元)
accept编号 :reject编号 :res编号 :end编号 :err键入 生产稳定id, (中文(简体) ), 连接不可知 - 它解决了呼叫者 承诺只要可以交付,就通过任何次的再连接.
每个对象测序+严格的FIFO
每个对象都有一个 后续权力 每个方向 Outbox) (中文(简体) ). 帧走
严格退出 一次性无抵押,所以通过订购的载体 接收器的
Sequencer 查看它们并经过;重排/再交付被缓冲
在内部 [nextExpected, +window],以下的后缀是重复(仅重置),
30多岁时的空隙要求 resync。 。 。 .
持久性发包箱+去dupe(精确地说)
每个 open编号 :send编号 :req (和每个回答) 持久排队 在此之前 击中了
电线. id 是一个 生产商-稳定独能键 -- -- 薄荷一次,再用
每一次重试,从不尝试。 接收器被 (peer, id) 为期7天
通过 bun-mq 的持久去dupe 表(它消耗了 + 重新启动),所以
脱地取胜于墓碑 即使是在原作之后 重送也会崩溃
已经交付.
徒劳/反弹法
- (a) 接收人出错类别 没有电线
final旗帜 接收器将代码分类为terminal或者说retryable(单位:千美元)finality.ts) (中文(简体) ). - (b) 核准的最终结果 - 说吧
close编号 :err杀死物体的,是荣耀的 仅来自经核实的参与者;否决未经授权的终局框架 之前,它甚至可以 扰动以下的边界. - (c) 完全反弹 - 未知对象的框( 或错误的地址)
输出一个终端
err墓志铭 数字 (不是每个名字),所以 稍后为合法open同一名称仍然有效. - (d) 一个
err永远没有err- 一个无法运行的err永远无法反弹. - (e) 清理终端 - 一个终端回答/弹出 清除发送者的行动和
输出 a
bridge/futile活动.
握手(全跨层拖拉)
L1通过 bun-reliable-ws + 键 cborCodec + 键 asAuthProvider认证
手抄本 绑定整个谈判的tuple -- -- 版本、功能、选择
每个槽中的名称( e)auth, (中文(简体) ), e2eSeal, (中文(简体) ), e2eSign, (中文(简体) ), codec, (中文(简体) ), 已调试
(作为谈判的地物),和 connId - 所以降级约束是
完成所有层.
这个 auth 选择是一个值,需要它
createDialerSubstrate 编号 : createListenerSubstrate 带 auth: () => AuthProvider 或者说 auth: "none"。没有默认和第三个选项,
因此,部署不能通过留下一些东西来达成一个未经证实的联系
出; 写入 auth: "none" 和SSH一样谨慎 Ciphers none,这是
当警告实现的时候, 两个广告 auth 名称列表和
电线提供商来源于这个值,所以他们不能不同意.
这个 authorize 门也是需要的 也是因为这个原因
auth 解决WHO的同伴. authorize 解决它可能做什么,它是
因此授权不能最终终止
通过一个忽略。 它需要一个 拦截器数字 : (ctx) => Denial | void, (中文(简体) ),
无法返回的地方 deny(code) 拒绝.
import { createListenerSubstrate, scopeAuthorizer, compositeInterceptor, deny, ALLOW_ALL } from "@claudewerk/bridge-substrate";
createListenerSubstrate({
peerId: "gate",
// One key record per peer, chosen by the id the peer claimed in its hello.
auth: substrateAuth({ myKey, peerRecordFor: (peer) => keyRecords.get(peer), method: AUTH_METHOD }),
authorize: scopeAuthorizer({
scopesOf: (peer) => keyRecords.get(peer)?.scopes, // unknown peer -> refused
matrix: { producer: ["open", "send", "close"], admin: ["*"] },
}),
});
城门所坐之处是其全部价值. 每一个入境业务都提供
在地址和动词验证后,将“和”改为“和” 在此之前 对象查询, 即
测序器、持久承诺和跳入- 所以一个被拒绝的对等点生成不
在此对象中,不移动接收边框,并且从未被告知其操作
已经接受。 进入的溪流 establish 穿过同一个链子,被拒绝
与 establish_fail{unauthorized} 在分配标签之前; objKind: "stream" 这不是绕出大门的路.
这是一个CHAIN,因为观察是常见的情况:一个返回的拦截器 没有任何允许,所以追踪和审计 是成员与政策 首先拒绝停止其他的.
authorize: compositeInterceptor([
(ctx) => void log.info("inbound", { peer: ctx.peer, verb: ctx.verb, obj: ctx.obj }),
(ctx) => (ctx.obj.startsWith("admin/") ? deny("unauthorized") : undefined),
scopeAuthorizer({ scopesOf, matrix }),
]);
运出的政策是逐一拒绝 并称共同键自动 scopeAllows
而不是重复规则 -- -- 一个定义,两个呼叫者. ALLOW_ALL 这是
命名退出并还原A1前态势:任何经认证的对等方都可能这样做
任何关于这个连接. ctx.cap 带有不透明能力符号
显示打开器( o)open(kind, addr, { cap }) 编号 : openStream(addr, { cap }); 计 数
底盘渡过它,从不剖析.
端对端加密接合
src/e2e.ts 这是什么 e2eSeal 财务报告和审定财务报表 e2eSign 插槽名称的意思。 一个登记处
每个插槽都持有这个构建工具的套间;一个主机的广告是准确的
这些名字和解决一个谈判名称 通过同一地图,所以
列表和线条不能漂移。 未经执行的谈判名称是:
拒绝在准备, 从来没有跳过.
今天运送的唯一套房是 none这是诚实的答案
点对点部署: TLS 已经覆盖每个跳, 以及真正的套房
中继器必须带有无法读取的内容。 增加一是二,你觉得呢
登记条目,谈判不变.
可观察性(可插、供应商中立)
底物保持无依赖状态,但有工具。 四个狭隘的关切 --
Logger 编号 : Metrics 编号 : Events 编号 : Tracer - 每一个都有 无效 默认( 0)
费用)a 复合 (fan-out to N.) (英语). 通过一个 observability 包到主机
工厂; 重汇 (OTel, Prometheus) 是适配器 在接口后面, 永远不会
道诸相.
import { createListenerSubstrate, observe, inMemoryMetrics, ALLOW_ALL } from "@claudewerk/bridge-substrate";
const metrics = inMemoryMetrics(); // snapshot() feeds a dashboard
createListenerSubstrate({ peerId: "host", authorize: ALLOW_ALL, auth: "none", observability: observe({ metrics }) });
// ... later:
metrics.snapshot().gauges.filter(g => g.name === "substrate.outbox.depth"); // per-object backlog
默认是 NO_OBSERVABILITY ——无文书是规范而自由. 接收
信号 : substrate.ops.enqueued/acked, (中文(简体) ), substrate.outbox.depth{obj} (每个对象)
积压), substrate.ack.latency_ms 编号 : substrate.answer.latency_ms, (中文(简体) ),
substrate.ingress.op{verb}, (中文(简体) ), substrate.dedupe.duplicate, (中文(简体) ), substrate.seq.deferred, (中文(简体) ),
substrate.authorize.denied{verb,objKind,code}, (中文(简体) ), substrate.bounce, (中文(简体) ),
substrate.futile{code}, (中文(简体) ), substrate.conn.ready/ended。适配器:
consoleLogger/Metrics/Events (调试), inMemoryMetrics (磁盘); OTel
适配器是预定的下一个。 见 src/observe.ts + 键
src/observe-adapters.ts。 。 。 .
版式
src/
wire.ts Env / Frame types, verbs, objKind, address validation
observe.ts pluggable observability: Logger/Metrics/Events/Tracer, null + composite
observe-adapters.ts console + in-memory sinks (OTel deferred)
codec.ts cborCodec as a WS Codec (zero glue)
auth.ts asAuthProvider as a WS AuthProvider (zero glue)
host.ts createDialer/ListenerSubstrate; capabilities; per-peer rebind
link.ts the structural Link surface (subset of ReliableConnection)
substrate-core.ts the engine: object registry, routing, the two ack levels, laws
(platform-neutral: no bun:, no node:, no carrier)
substrate.ts the Bun door: the same class plus `dbPath` (a SQLite file)
client.ts the browser entry: the engine and client surface, no Bun-only module
async-sink.ts async profile: the AsyncSink contract + the per-object commit chain
feed.ts relay outbound: external-seq feed (stream seq + prev), FeedHost
memory-stream.ts JetStream-shaped memory stream, memorySink, memoryFeed (fakes)
journal.ts journal form: DurableJournal, its serial writer, restore
idb-journal.ts the browser outbox store over IndexedDB
client-substrate.ts createClientSubstrate: restore from the journal, then build
object.ts SubstrateObject: state machine + app-facing verbs
outbox.ts durable per-object outbox, strict-FIFO one-in-flight, receipt-fenced
sequencer.ts per-object in-seq delivery, gap buffer, out_of_seq, resync
dedupe.ts durable (peer,id) dedupe ledger over the bun-mq Store
finality.ts error-class derivation + per-op-id tombstones
test/
handshake.test.ts four packages compose; connId agrees on both ends
session.test.ts open+accept, req->res, send->ack, reject, close
reliability.test.ts exactly-once across a forced disconnect; strict FIFO under redelivery
futility.test.ts non-participant close rejected; unknown-object bounce+tombstone; bad address
auth.test.ts wrong key fails; tampered tuple breaks the proof
primitives.test.ts unit tests for outbox / sequencer / dedupe
这证明什么
超过一个 真实 两者之间的可靠ws连接 Substrate 实例 :
- 第二届会议
openA->B(接受),req- > (中文)res单向send- > (中文)ack· ; - 完全通过强制断开+重接 (再发送+去dupe);
- 严格 FIFO (一) 在重新交付下保存的每个物体;
- (单位:千美元)
close编号 :err从a 非参加者 被拒绝(物体还活着); - 一个框架到一个 未知对象 输出一个终端
err+ 个人编号 墓碑(后为正文)open- 名称不变; - 认证/降级: 错误的密钥出错; 被篡改过的谈判拖曳 (connId /) 跨层集)打破了方向证明.
被推迟(清洁接缝,未建)
F1CORE在这里停留。 后来的阶段在代码中命名了接合:
- F2 批量转让 -- -- 数据
chunk动词 +sendChunk(已宣布), - 没有电线). - F3 同行生命周期 -- 活动/无法接触/固态/已终止(底物) 是每平方 + 可重置,即钩子).
- F4发现+提议/同意/合法 ——同路由器上的新动词.
- F5状态@ 1 (Yjs) - 第三个
objKind在同一物体机器上.
序列状态和墓碑为内饰(耐用外框+去dupe载体)
完全通过重新启动; 持久测序器/通石持久性是一个
后相接相接相. 见 FINDINGS.md 为所降示的经典.