Skip to content

05 · 写入与提交后分发

ingest_event 是 Buzz 最重要的权威管线。WebSocket EVENT 和多个 HTTP bridge 最终都应进入同一套校验与持久化逻辑,避免“换一个协议入口就绕过权限”。成功响应的语义是 durable acceptance,而不是所有下游副作用都已完成。

1. 总流程

text
EVENT / HTTP payload

        ├─ auth + scope + rate limit
        ├─ size / timestamp / known-kind policy
        ├─ event id + Schnorr signature
        ├─ tenant / global-vs-channel shape
        ├─ membership + kind-specific authorization
        ├─ thread/edit/vote/diff/envelope validation

 PostgreSQL transaction
   ├─ insert/replace event
   ├─ update relational projections
   └─ commit

        ├─ bounded audit enqueue (awaited)
        └─ spawned post-commit work
             ├─ Redis publish
             ├─ local fan-out
             └─ workflow trigger / other derived work

2. 通用检查

写入入口会先解决与具体产品无关的约束:

  • 认证主体与 event pubkey 的关系。
  • endpoint 所需 scope。
  • event 大小、时间窗口和 tag 形状。
  • ID/签名验证放入 blocking pool。
  • kind 是否允许、是 ephemeral 还是 persistent。
  • 事件应是 global-only、requires-h,还是允许两者。

这些检查越早失败,越少占用数据库事务和昂贵的关系查询。

3. 频道与成员授权

对于 channel-scoped 事件,Relay 从 h tag 解析频道 UUID,再查询同 community 的频道与成员。权限不只看“是成员”:

  • 私有频道要求当前 membership。
  • 管理事件要求 owner/admin 等角色。
  • Bot 角色不进入普通角色高低层级,避免“数值更高”意外继承管理能力。
  • 频道归属 community 必须与 TenantContext 一致。

权限检查与数据库投影更新应在同一事务视野中完成,减少 TOCTOU 窗口。

4. Kind-specific 语义校验

统一管线并不意味着所有 kind 只做一套检查。ingest.rs 内有大量领域规则:

事件额外约束
回复父事件存在、同社区同频道;解析 NIP-10 root/reply;深度上限 100
编辑原作者可编辑;Agent owning human 可编辑其 Agent 消息;仍需当前私有频道权限
论坛投票目标必须是同频道的 forum post/comment,方向合法
Diff内容上限约 60 KiB,repo/commit/branch/PR 元数据形状校验
EngramNIP-AE envelope、可见性与 owner 关系
Persona/Teamparameterized coordinate 与管理权限
Agent metric指标 envelope 和隐私策略
Workflow definitionYAML/schema、owner authority 与频道归属

把规则放进 Relay 而不是相信客户端,保证 Web、CLI、Desktop 与恶意自制客户端得到同一边界。

5. 持久事件与 ephemeral 事件

持久事件

进入 buzz-db,处理普通插入、replaceable coordinate、删除语义和关系投影。commit 成功后才可返回 accepted。

Ephemeral

Presence、typing、observer frame 等不进入长期事件表,但仍经过签名、租户、频道和权限检查,然后直接进入 PubSub/fan-out。HTTP bridge 可能拒绝某些 ephemeral kind,因为无状态 HTTP 发布缺少与 WS 生命周期一致的语义。

6. durable acceptance 的精确含义

dispatch_persistent_event 把工作分成两侧:

text
before return OK                  after commit / async
──────────────────────────────   ─────────────────────────
DB committed                      Redis cross-pod publish
event authoritative               local subscription fan-out
bounded audit handoff awaited     workflow execution kick

选择这样分层的理由:

  • 不能在 commit 前发布,否则订阅者可能看到最终回滚的“幽灵事件”。
  • 不应让某个慢订阅者或 Redis 短暂故障拉长所有写请求。
  • 客户端需要一个稳定承诺:accepted 等于事实已持久化。

代价是 commit 与通知之间存在窗口。订阅者必须支持重连后用 since 补历史;工作流和 Push 需要 outbox/可恢复设计覆盖重要副作用。

7. 本地回声与跨 pod 去重

当前 pod 写入后既会本地 fan-out,也会发布到 Redis。Redis 消息又可能回到原 pod,因此 Relay 维护按 (community, event_id) 作用域的 local-echo cache:

  1. 本地提交时标记 echo。
  2. Redis 订阅回流时识别并跳过重复本地派发。
  3. community 进入 key,避免同 ID 在租户之间产生错误关联。

这只是投递去重,不改变事件 ID 在数据库中的幂等约束。

8. Fan-out 仍是安全检查点

候选订阅者来自内存索引,但发送前 filter_fanout_by_access 仍检查:

  • receiver 连接的 community label。
  • author-only / shared-gated 等主体门。
  • 私有频道当前 membership;成员刚被移除时不能靠旧订阅继续收消息。

Redis topic、订阅索引和缓存都不是授权证明。任何缓存错误至多影响候选集合,不应扩大可见性。

9. side effects 的事务边界

频道管理事件常同时更新关系投影和发布 discovery/membership snapshot/delta。原则是:

  • 决定权限的投影在事务内更新。
  • 可重建通知在 commit 后发布。
  • audit 使用独立的社区 hash-chain 服务记录操作语义。

若某个派生通知失败,重建/重新查询应能从数据库权威事实恢复,而不是要求回滚已经接受的用户事件。

10. 失败模型

失败点结果
签名/权限/形状失败拒绝,不开启或回滚事务
DB commit 失败拒绝,不 fan-out
audit bounded handoff 失败依当前 handler 策略暴露错误/记录严重问题,避免静默丢失
Redis publish 失败事实已接受;本 pod 可继续,本地外 pod 依赖恢复/历史补偿
单个订阅者队列满不回滚事实;慢消费者被隔离
workflow 失败记录 run 失败,不回滚触发事件

11. 源码入口

独立源码研究笔记 · 非 Buzz 官方文档