Skip to content

feat(presence): member_joined / member_left + 预留 host/capabilities/budgetHint (PR8)#195

Open
raysonmeng wants to merge 1 commit into
feat/v3-pr7-completionfrom
feat/v3-pr8-presence
Open

feat(presence): member_joined / member_left + 预留 host/capabilities/budgetHint (PR8)#195
raysonmeng wants to merge 1 commit into
feat/v3-pr7-completionfrom
feat/v3-pr8-presence

Conversation

@raysonmeng

Copy link
Copy Markdown
Owner

概要 / Summary

v3 §11.1 单团队 MVP 的 PR8(stacked,base = feat/v3-pr7-completion / #194)。broker 在房间成员到场/离场时合成 presence 事件(§11.1 bullet 9),让成员实时看到谁在房间里。

The broker synthesizes presence events as members arrive/leave a room (§11.1 bullet 9), so members see who is live.

改动 / Changes

  • src/presence.tsbuildPresenceEnvelope:广播(无 to)、online_only(临时,绝不为离线补投落库)、payload {displayName, host, capabilities, budgetHint}budgetHint 是 B 类预留,A 类 MVP 忽略
  • src/broker.tsaddTopicMember/removeTopicMember 现返回成员转换:0→1member_joined(同身份第二条连接重复广播);→0(最后一条连接离开)发 member_left,从 unsubscribe close() 两路径发出 —— 崩溃断连也会离场。from.agentId 是 broker 鉴权身份(不可伪造)。hello 可带预留 presence,经 sanitizePresence 校验(丢弃非字符串/嵌套对象/__proto__ 等畸形输入)。
  • src/broker-client.ts — hello 帧可选携带 presence 元数据。
  • member_joined 在 drain joiner 自身 backlog 之前发出,未来真异步 Store 也不会把它排到 close()member_left 之后(幽灵顺序)。

测试 / Tests

  • presence(3)+ sanitize-presence 边界(4,含 __proto__ + 畸形输入)+ broker-presence 集成(3:既有订阅者见到带预留 meta 的 join + 自跳过、断连 member_left、多连接引用计数 = join 一次 / 末次才 leave)。
  • broker-routing 的 WsClient 过滤 presence churn(那些用例断言已发布信封的路由,非 presence)。
  • bun run check 全绿:1786 pass / 0 fail,bundle 同步,版本对齐。

Cross-review

4 轮(前台并行),修 1 latent ordering(join-after-drain)+ 1 HIGH 测试缺口(sanitizePresence 信任边界零对抗性输入覆盖);连续两轮 0 真实 issue 收敛。

铁律自检 / Invariants

  • ✅ 控制/数据面分离:presence payload 仅结构化元数据,无文件内容。
  • online_only 绝不落库;from.agentId 不可伪造;hello presence 经 sanitize。
  • ✅ 不碰 multi-pair;BrokerClientOptions.presence 可选不破坏既有调用;无新增运行时依赖。
  • ⚠️ 依赖 stacked base feat/v3-pr7-completion(feat(completion): task_completed 事件 + 发布节流 + Stop hook 发布 (PR7) #194)。
  • ℹ️ 仅播 join/leave 增量,roster 快照属独立关注点(后续 PR)。

🤖 Generated with Claude Code

…dgetHint (PR8)

v3 §11.1 单团队 MVP 的 PR8。broker 在房间成员到场/离场时合成 presence 事件
(§11.1 bullet 9),让成员实时看到谁在房间里。

PR8 of the v3 §11.1 single-team MVP. The broker synthesizes presence events as
members arrive/leave a room, so members see who is live.

- src/presence.ts: buildPresenceEnvelope — broadcast, online_only (EPHEMERAL,
  never persisted for offline replay), payload {displayName, host, capabilities,
  budgetHint}. budgetHint is reserved for the B-class; the A-class MVP ignores it.
- src/broker.ts: addTopicMember/removeTopicMember now return the membership
  transition. member_joined on the 0→1 transition (a 2nd connection for the same
  identity does NOT re-announce); member_left on →0 (last connection leaving),
  emitted from BOTH unsubscribe AND close() so a crash-disconnect still leaves.
  from.agentId is the broker-authenticated identity (unspoofable). hello may
  carry a reserved presence blob, validated by sanitizePresence (drops malformed
  input — non-string fields, nested objects, __proto__).
- src/broker-client.ts: optional presence meta in the hello frame.

member_joined is emitted BEFORE draining the joiner's own backlog so a future
truly-async Store can't reorder it after a close()-emitted member_left.

Tests: presence (3) + sanitize-presence boundary (4, incl. __proto__ + malformed)
+ broker-presence integration (3: existing-subscriber sees join with reserved
meta + self-skip, member_left on disconnect, multi-connection refcount = join
once / leave only on last). broker-routing WsClient filters presence churn (those
tests assert routing of published envelopes, not presence).

Cross-review: 4 rounds (foreground), fixed 1 latent ordering (join-after-drain)
+ 1 HIGH test gap (sanitizePresence had zero adversarial-input coverage);
converged on 2 consecutive clean rounds. bun run check green: 1786 pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant