Skip to content

feat(sharing): membership lifecycle v1.1 — invite/accept, revoke rotation, headless sync - #65

Open
yexiyue wants to merge 2 commits into
developfrom
feature/sharing-v1.1
Open

feat(sharing): membership lifecycle v1.1 — invite/accept, revoke rotation, headless sync#65
yexiyue wants to merge 2 commits into
developfrom
feature/sharing-v1.1

Conversation

@yexiyue

@yexiyue yexiyue commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

概述

e2e-sharing-v1(v0.5.0,已发布)的加密内核之上,把分享的成员生命周期补全——四个驱动问题都来自真机双设备测试。OpenSpec change:sharing-v1-1(本地,openspec 目录 gitignore)。

包含

Q1 撤销真切断(兑现 e2e-sharing-v1 延后的 5.4)

  • revoke_member 发完 Revoke op 后调 keys::rotate_workspace_key(bump key_version + 新 self-Lockbox,保留历史 = lazy,无前向保密);剩余成员在收到权限链广播时反应式重取新 key(coordinatorfull_sync::fetch_workspace_key)。被移除设备拿不到新 Lockbox → 解不开新内容
  • 入站 gossip 解密前按 is_authorized(source) 预检 → 即时丢弃被移除设备的广播(doc + awareness)。
  • 被移除设备收 AppEvent::MemberRevoked → 自动退订 + 提示;旧的已同步内容仍可读(威胁模型物理边界)。

Q2 分享改为「邀请 → 接受」握手

  • WorkspaceRequest::ShareInvitation + WorkspaceResponse::ShareInvitationResultAppCore::invite_device 阻塞等对方接受/拒绝(复用配对的 request-response pending 关联),接受后才 grant_collaborator——未接受不授权。
  • ShareInvitationReceived 事件 → ShareInvitationDialog(走通知队列)。

Q3 自动接受邀请设置

  • 设置「网络」tab 全局开关 autoAcceptInvitations(默认关,持久化);开启后对已配对设备的邀请自动接受、不弹窗。

Q4 未打开工作区可同步(headless 懒打开)

  • AppCore::ensure_open_for_sync:被授权对端 ListWorkspaces/DocList/WorkspaceKey 时,按需以 sync-only(不订阅 gossip) 打开本机未在窗口打开的工作区,使其可被发现/拉取。仍按 role_of 过滤,未授权看不到也拉不到。

重构

  • 抽出 useCountdown hook,去掉 ShareInvitationDialog/PairingRequestDialog 字节级重复的倒计时 effect。

验证

  • core 106 tests 全绿(含新增 rotation_cuts_off_old_version_holder);cargo clippy(core + 桌面)-D warnings 干净;tsc + biome 干净;桌面二进制链接成功。
  • 代码审查(3 路 × 21 agent 对抗式审 + 逐项验证):18 条发现里 16 条为误报/no-op,唯一被标 bug 的也核实不成立——核心层授权门/密钥轮换/IPC 映射正确且合规。仅采纳 2 处纯前端清理(useCountdown + 补 Lingui 文案)。

⚠️ 待真双机实测(CI/单实例覆盖不到)

合并前建议两台真机过一遍:① owner 邀请 → B 弹窗接受 → 可同步;② owner 移除 B → B 在线即停 + 轮换后解不开新内容 + B 收到「已被移出」;③ 另一台设备能看到并拉取 A 未打开的工作区;④ 验证 invite_to_workspace 的 request-response 超时窗口够用户响应(配对同款机制)。

安全 / 边界(诚实声明)

  • 撤销是 lazy 的:被移除设备对离开前已同步的旧内容(旧 key_version永久可读、无法远程删除(任何 E2E 系统的物理边界)。在线切断不是密码学切断(对方仍持旧 key、能离线解已抓的旧密文)。
  • 邀请「自动接受」是用户主动 opt-in,等价于把「已配对」提升为「信任邀请」,仅对已配对设备生效。

不在本 PR(后续)

  • 接受邀请后 auto-join(需定落盘位置)、被分享方持久「分享给我」清单、并发轮换收敛(单 owner moot)、gossip 授权预检缓存、en 翻译补齐。
  • 移动端(SwarmNote-RN)对齐:邀请对话框 / 被移除提示 / 轮换对端表现的 RN UI(另起 OpenSpec)。

yexiyue added 2 commits June 9, 2026 11:05
…tion, headless sync

Completes the sharing lifecycle on top of e2e-sharing-v1 (v0.5.0).

Revocation now truly cuts off (fulfills e2e-sharing-v1's deferred 5.4):
- revoke_member rotates the workspace key (keys::rotate_workspace_key: bump
  key_version + new self-Lockbox, history retained — lazy, no forward
  secrecy); remaining members re-fetch the new key reactively on the
  permission-op broadcast (coordinator -> full_sync::fetch_workspace_key).
  The removed device gets no new Lockbox so it cannot decrypt new content.
- inbound gossip is gated on is_authorized(source) before decrypt, so a
  revoked device's broadcasts (doc + awareness) are dropped immediately.
- the revoked device gets AppEvent::MemberRevoked, auto-unsubscribes, and is
  notified; already-synced content stays readable (threat model).

Sharing is now invite->accept (not a silent owner-side grant):
- WorkspaceRequest::ShareInvitation + ShareInvitationResult; AppCore::
  invite_device blocks on the recipient's accept/decline (reusing pairing's
  request-response pending correlation) and only grants on accept.
- ShareInvitationReceived event -> ShareInvitationDialog (notification
  queue); an "auto-accept invitations" preference (default off) skips the
  dialog for paired devices.

Un-opened workspaces are now syncable (headless lazy-open):
- AppCore::ensure_open_for_sync opens a member workspace sync-only (no gossip
  subscribe) on demand when an authorized peer lists/pulls it, so a workspace
  need not be open in a window to be discovered/synced. Still role-filtered.

Refactor: extract the duplicated dialog countdown into useCountdown.

core: 106 tests green, clippy clean, tsc/biome clean.
- blog e2e-encrypted-sharing: add section 10 "v1.1 续集" (invite/accept,
  revoke cutoff + key rotation, headless lazy-open) + fix Mermaid render
  (Box is a reserved sequenceDiagram keyword -> LB; drop the [..|..] frame
  text that broke parsing).
- 11-threat-model: revocation is now two-stage (online cutoff + key
  rotation), invite consent, auto-accept opt-in, headless does not loosen
  the role gate.
- 05-sharing: add "v1.1 实现增量" section.
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