Skip to content

feat(contacts): 支持桌面设备间安全同步 - #1214

Merged
MagicLizi merged 23 commits into
mainfrom
contacts-device-sync
Jul 31, 2026
Merged

feat(contacts): 支持桌面设备间安全同步#1214
MagicLizi merged 23 commits into
mainfrom
contacts-device-sync

Conversation

@zqchris

@zqchris zqchris commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

目标:让同一账号下已登录且在线的 Desktop 自动同步智能通讯录,局域网优先直连,失败后通过现有 Device Link 转发密文。
非目标:本 PR 不做 Mobile、不增加服务端存储,也不提供两端错开在线时的离线队列。
产品行为:每台 Desktop 在「设置 → 智能通讯录」开启一次;之后自动同步,也可点「立即同步」。
失效边界:账号、区域或连接切换时立即清理旧 owner 的在途状态,避免旧账号数据落到新连接。
验收口径:同步全程是确定性程序逻辑,不调用模型、不消耗 AI token;N 台设备经任意在线路径传播后最终收敛。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:智能通讯录 Desktop 设备间安全同步(产品讨论)
  • 本 PR 包含:
    • Desktop ↔ Desktop 同步;同一 Wi-Fi 下用局域网直连,端点通过设备密钥 request / ACK 双向认证,连不上或认证失败时使用现有 Device Link relay 转发端到端密文。
    • X25519 + HKDF + AES-256-GCM 设备间加密;设备私钥使用 Electron safeStorage 加密、按账号隔离落盘;Linux 若退化到不安全的 basic_text 后端则 fail closed;首次从同账号认证 relay 看到设备公钥后固定,后续公钥变化 fail closed。
    • 状态式增量合并:字段级并发编辑、删除墓碑、分组/关系等可以幂等合并;三台及更多设备可沿任意在线路径最终收敛。
    • 跨设备确定性:冲突裁决采用规范化 JSON 与 locale 无关排序;入站状态要求设备 clocks 覆盖所有内容 stamp,本地编辑只扫描设备时钟而不全表扫描。
    • 防御性边界:磁盘投影逐表校验完整字段、ID 唯一性和引用关系;单端入站状态有行数/时钟上限,合并后的并集也会在落库前重新校验,超限事务整体回滚;设备公钥 pin 最多 1,000 个。
    • Main 性能与资源边界:SQLite 状态读取/合并、delta、JSON、gzip 与加解密均在专用 worker_threads 内完成;Main 只接收 transferable 密文字节或小型结果。DB-bound worker 串行,普通 codec 最多 2 个活跃、8 个排队;账号切换会软取消并在原子事务边界后有界终止旧 owner 任务。
    • 慢速传输稳定性:分片传输按最后一个新分片续期,重复分片不能恶意保活;relay 仍逐片释放背压。
    • 共享 userData 多实例安全:通讯录开关通过 owner-scoped 意图文件在线性化锁内提交,进程崩溃后由链路持有者 fail closed 恢复关闭;设备密钥文件使用异步 PID/mtime heartbeat 跨进程锁串行创建/更新,避免阻塞 Electron Main 线程,锁内重读最新 pin 并用唯一临时文件替换;owner 切换、降级和异步 pin 均有 generation guard。
    • 投影自愈:即使 CRDT 状态未变化,也会核对当前 SQLite 投影;本地删除或改名解除唯一约束后,隐藏记录无需等待对端回包即可重新物化,并同步重建 FTS、通知界面;合法的合并后多身份数据不会被同步层截断。
    • FTS 自愈:同步后的索引重建失败会保留 dirty 状态,相同状态重投也会重试;重启时按完整索引内容检查,不再只比较行数。
    • 自动时机:首次开启、设备上线/重连、本地通讯录变化后去抖、每 30 分钟校准;另提供「立即同步」。
    • Telegram 群成员自动建档成功后也会触发同一条本地变更事件,不再等待 30 分钟校准。
    • 设置页展示开关、在线设备数、等待/同步/成功/错误状态、上次设备与传输路径;管理弹窗也提供立即同步入口;本地/未登录模式明确提示需登录且不能误开启。
  • 明确不包含:Mobile;新增服务端接口或通讯录落盘;设备不能同时在线时的离线中转;跨账号同步。
  • 用户可见变化:在「设置 → 个性化 → 智能通讯录」新增「在设备间同步」设置卡;每台参与设备需各开启一次。家里修改后,只要公司机器上的 Cindy 仍在线且已开启同步,就会自动收到;设备数量不限制为两台。
  • 是否存在 breaking change:无。独立 device-link:contacts:sync:v1 channel;旧客户端忽略未知 channel。

UI 变化

  • 设置页新增同步卡:开关、状态、在线设备数和「立即同步」按钮;通讯录管理弹窗头部同步展示状态和操作入口。
  • macOS 隔离沙盒已目检关闭态、开启/同步中、管理弹窗,Light / Dark 均已检查;临时截图未进入产品代码,PR 未附图。
  • 引用的设计规范:
    • DESIGN.md §4 Component Stylings / Buttons / Cards & Containers:复用设置页卡片层级、按钮高度、圆角与 hover/disabled 反馈。
    • DESIGN.md §10 Theme System & Token Reference:所有颜色使用现有语义 token,Light / Dark 共用实现。
    • DESIGN.md §11 Voice & Content:状态和错误文案保持简短、可操作,并同步落地 zh-CN / en / ja / ko。
    • DESIGN.md §14.4 Motion:同步旋转只挂在 HTML wrapper,使用语义 animate-spinner,并在 prefers-reduced-motion 下保持静止。

怎么验证的

自动验证

pnpm --filter desktop exec vitest run --pool=forks --maxWorkers=1 \
  src/main/contacts-sync/__tests__/driver.test.ts \
  src/main/contacts-sync/__tests__/crypto-wire.test.ts \
  src/main/contacts-sync/__tests__/lanTransport.test.ts \
  src/main/contacts-sync/__tests__/keyStore.test.ts \
  src/main/contacts-sync/__tests__/sender.test.ts \
  src/main/contacts-sync/__tests__/statusStore.test.ts \
  src/main/maker-host/__tests__/contactsChangeEvents.test.ts \
  src/main/maker-host/__tests__/contactsSettingsStore.test.ts \
  src/main/maker-host/__tests__/overrideSettingsFile.test.ts \
  src/main/im/telegram/__tests__/contactsAutoRegister.test.ts \
  src/main/maker-ipc/__tests__/contactsIpc.test.ts
结果:11 files / 65 tests passed(含共享 userData 持有者运行态与在线设备数跨进程回传、显式 Device Link 持有权隔离、被动实例立即同步请求转交及无持有者超时回退、异步设备密钥锁不阻塞 Main 事件循环、并发首次建钥与 peer pin 合并、owner/demote 等待态隔离、跨实例同步开关意图原子提交与崩溃后 fail-closed 恢复、关闭同步立即停传、联系人变更 token、key 握手响应独立节流、伪信标失败后按设备冷却并立即回退 relay,以及 relay 背压后保留同一 transfer、重试当前分片再续发)

pnpm --filter desktop exec vitest run --pool=forks --maxWorkers=1 \
  src/main/contacts-sync/__tests__/{codecWorkerClient,crypto-wire,driver,keyStore,lanTransport,sender,statusStore}.test.ts
结果:7 files / 55 tests passed(含 Linux basic_text fail-closed、发送端 128 MiB 解压边界、worker transferable 边界、DB 任务串行与 owner abort、128 分片冷启动共享 prepare、12 台在线设备顺序 fan-out、慢速分片续期、peer pin 上限及本地重物化通知)

pnpm --filter @cindy/device-link exec vitest run --pool=threads --maxWorkers=1
结果:5 files / 100 tests passed

pnpm --filter @cindy/device-link build
结果:passed

pnpm --filter @cindy/maker-core exec vitest run --pool=forks --maxWorkers=1 src/contacts
结果:6 files / 120 tests passed(含三设备传播、并发字段、删除墓碑、首次激活超限回滚、仅大小写不同分组、同状态旧投影重新物化、worker 合并时 SQLite 投影与 FTS 原子提交/回滚、FTS 自愈、启动批量投影、幂等/交换/结合、确定性裁决、时钟覆盖、合并后上限和磁盘边界)

pnpm --filter desktop exec vite build --config vite.contacts-sync-codec-worker.config.ts --ssr src/main/contacts-sync/contactsSyncCodecWorker.ts --outDir <temp>
结果:passed;随后用真实 X25519 密钥和两份真实 SQLite 执行 prepare → encode → decode → apply,目标库联系人落盘验证 passed

pnpm --filter desktop run --if-present typecheck
结果:passed

pnpm --filter @cindy/maker-remote-ssh exec vitest run --pool=threads --maxWorkers=1 src/__tests__/remoteHostForward.test.ts
结果:1 file / 22 tests passed(复验旧 CI 唯一失败项;本地 59ms 通过,判定为与本 PR 无关的 runner 超时抖动)

pnpm --filter @cindy/maker-core run --if-present typecheck
结果:该 package 无 typecheck script,按仓库门禁跳过

pnpm check:i18n
结果:passed(zh-CN / en / ja / ko 共 6164 个 key 一致)

pnpm check:i18n-glossary
结果:passed

pnpm check:dco upstream/main..HEAD
结果:17 commits signed off,5 merge commits exempt

pnpm test:unit
结果:功能稳定后完整通过过一次(Desktop 104.6s、Mobile 9.7s、其余 workspace 全绿)。合入最新 main 后复跑,Desktop 的 Vitest threads 在所有断言结束时触发 Node/V8 worker teardown SIGSEGV;排除 contactsIpc 后仍能复现,macOS crash report 落在 V8 weak callback,未出现通讯录/SQLite assertion failure。交由 PR CI 在标准 runner 复验。

手工验证

  • 平台:macOS,独立 checkout + 独立端口 + 独立 userData 沙盒。
  • 检查路径:设置 → 个性化 → 智能通讯录。
  • 已检查:同步关闭、开启、同步中、无在线设备、管理弹窗、开关恢复;Light / Dark 两种主题。
  • 已确认:同步卡和管理入口均无预览/调试脚手架,关闭沙盒时没有停止或复用其他 checkout 的实例。

未执行的验证

  • 尚未用两台物理 Desktop 做同一局域网直连和异地 relay 的端到端实机验收;当前由传输、加密、分片、状态合并和 driver 单测覆盖。
  • 尚未在 Windows / Linux 物理机验证局域网防火墙行为;直连失败会自动回退 relay。
  • Mobile 明确留待后续独立 PR,避免本次同时引入移动端原生网络权限和冷更范围。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:首次设备公钥采用同账号认证 relay 上的 TOFU 固定;可抵御被动读取和后续换钥,但没有额外扫码/人工比对来抵御服务端在首次交换时主动替换双方公钥。

影响与回滚

  • 影响范围:
    • 智能通讯录 SQLite 新增 contacts_sync_state 单例表,保存可重复合并状态和本地投影;不改写既有业务表 schema。
    • 新增独立 Device Link payload channel;relay 只转发,不保存通讯录,线上/局域网均传同一份 AES-GCM 密文。
    • 局域网公开信标只用于发现候选端点;只有认证 ACK 才算直连成功,未认证 TCP 同时最多保留 32 条且单包/超时均有上限;候选端点认证失败后按设备冷却 60 秒,期间所有分片立即走 relay,避免重放信标按分片累积超时。
    • macOS 包增加 NSLocalNetworkUsageDescription;Windows / Linux 不改变原生权限或 Mobile runtime fingerprint。
    • 两端必须有一段同时在线的时间;完全错开在线时不会同步。
  • 回滚 / 降级方式:
    • 用户可在每台设备关闭「在设备间同步」,立即停止 LAN 广播和 relay 发送,不影响本地通讯录使用。
    • 代码回滚后,新增表、owner-scoped 设置和安全存储密钥文件保持惰性,不会被旧版本读取;没有服务端通讯录需要清理。
    • 局域网不可用或权限被拒绝时自动降级到 relay 密文转发。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO
  • UI 改动已在「UI 变化」注明引用的设计规范章节(不涉及 UI 则跳过)
  • 未提交凭证、令牌或授权文件
  • 已补充必要文档
  • 已确认测试结果或说明未执行原因

zqchris added 4 commits July 31, 2026 18:06
Signed-off-by: zqchris <chrisz83@gmail.com>
Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 10:50
@zqchris
zqchris requested a review from a team as a code owner July 31, 2026 10:50
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 为 Desktop 智能通讯录新增安全的跨设备同步能力。

  • 使用经过设备密钥双向认证的局域网直连,并在失败时回退到 Device Link 密文中继。
  • 引入 X25519、HKDF 与 AES-256-GCM 加密、设备密钥安全存储及公钥固定。
  • 新增可增量合并的通讯录同步状态、SQLite 投影与 FTS 自愈机制。
  • 将数据库、压缩和加解密任务移入受并发与队列限制的 worker threads。
  • 增加同步开关、状态展示、立即同步入口以及多语言文案。
  • 已为此前报告的信标重放、未鉴权连接资源耗尽和逐分片回退延迟补充认证 ACK、连接上限及设备级冷却。

Confidence Score: 5/5

当前代码已解决先前报告的问题,未发现仍会阻止合并的故障。

认证 ACK 阻止伪端点冒充成功投递,入站连接具有数量、大小和超时上限,失败端点的冷却会阻止重放信标让后续分片重复等待,同时每次直连失败仍会回退 relay;没有阻塞性故障残留。

Important Files Changed

Filename Overview
apps/desktop/src/main/contacts-sync/lanTransport.ts 实现局域网发现、双向认证、连接资源边界和失败后的设备级直连冷却。
apps/desktop/src/main/contacts-sync/driver.ts 编排账号隔离、运行时所有权、自动同步时机、入站处理及状态发布。
apps/desktop/src/main/contacts-sync/keyStore.ts 实现按账号隔离的设备密钥存储、公钥固定和跨进程锁定。
apps/desktop/src/main/contacts-sync/sender.ts 按分片顺序发送同步状态,并在局域网直连失败时通过 relay 回退。
packages/maker-core/src/contacts/sync/repository.ts 将同步状态合并、投影物化和索引维护纳入 SQLite 事务边界。
packages/device-link/src/contactsSyncProtocol.ts 为通讯录同步增加独立且受校验的 Device Link payload 协议。
apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx 在智能通讯录设置中增加跨设备同步开关、状态和立即同步操作。

Sequence Diagram

sequenceDiagram
    participant A as Desktop A
    participant LAN as 局域网候选端点
    participant Relay as Device Link Relay
    participant B as Desktop B
    A->>LAN: 发现 B 的候选端点
    A->>B: 密文分片 + 设备密钥认证请求
    alt B 返回有效认证 ACK
        B-->>A: 已认证 ACK
    else 直连或认证失败
        A->>Relay: 转发同一密文分片
        Relay->>B: 密文分片
    end
    B->>B: Worker 解密、校验、合并并更新 SQLite/FTS
Loading

Reviews (17): Last reviewed commit: "fix(contacts): align normalized identity..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/contacts-sync/lanTransport.ts
Signed-off-by: zqchris <chrisz83@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ead6855a95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/contacts-sync/driver.ts Outdated
Comment thread apps/desktop/src/main/contacts-sync/wire.ts Outdated
Comment thread apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 为「智能通讯录」新增 Desktop ↔ Desktop 的安全自动同步能力:同一账号下在线设备之间优先走局域网直连,失败回退到现有 Device Link relay 转发端到端密文;同步数据采用确定性状态合并,目标是多设备最终收敛且不依赖模型推理。

Changes:

  • @cindy/maker-core 引入通讯录同步的 CRDT 状态模型、合并/增量、校验、SQLite 持久化与快照读写,并在 MakerContactsStore 暴露激活/读取/合并入口。
  • Desktop main 侧新增 contacts-sync 驱动(密钥、安全存储、分片 wire、LAN 直连、relay 回退、状态机与持久化),并通过 IPC + preload 暴露给 renderer。
  • Desktop 设置页与管理弹窗新增「在我的设备之间同步」开关/状态/立即同步入口,并补齐四语文案与术语表条目。

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/maker-core/src/index.ts 导出通讯录同步类型与增量生成函数供 desktop 侧复用。
packages/maker-core/src/contacts/sync/types.ts 定义同步契约(状态、stamp/clock、快照结构、membership 复合键)。
packages/maker-core/src/contacts/sync/merge.ts 实现状态合并、stamp 比较、增量生成与下一次本地 stamp 生成。
packages/maker-core/src/contacts/sync/capture.ts 将 SQLite 两次快照差异捕获进 CRDT 状态(字段级 stamp/删除墓碑)。
packages/maker-core/src/contacts/sync/materialize.ts 将合并后的 CRDT 状态物化为 SQLite 可写快照,并做唯一约束冲突的确定性裁决。
packages/maker-core/src/contacts/sync/snapshot.ts 读写通讯录主表快照(用于同步层投影与写回)。
packages/maker-core/src/contacts/sync/validation.ts 深度校验来自链路/磁盘的同步状态,防畸形/超量数据进入 SQLite。
packages/maker-core/src/contacts/sync/repository.ts 同步状态的 SQLite singleton 持久化与 reconcile/merge 流程。
packages/maker-core/src/contacts/store.ts 接入同步仓库并暴露 activate/read/merge,同步写回后安全重建 FTS。
packages/maker-core/src/contacts/schema.ts v3 migration:新增 contacts_sync_state 表。
packages/maker-core/src/contacts/tests/contactsSync.test.ts maker-core 侧三设备传播、幂等/交换/结合、增量与安全校验等测试。
i18n/GLOSSARY.md 新增 “End-to-end encryption” 术语条目(待讨论)。
i18n/glossary.json 新增 end-to-end-encryption 术语(proposed,含四语翻译)。
apps/desktop/src/renderer/vite-env.d.ts 扩展 ElectronAPI:同步状态读取/开关/立即同步 + 状态订阅。
apps/desktop/src/renderer/lib/contactsService.ts 定义 ContactsDeviceSyncStatus 并封装 sync IPC 调用与订阅。
apps/desktop/src/renderer/i18n/locales/zh-CN/common.json 增加同步卡片文案与错误提示(zh-CN)。
apps/desktop/src/renderer/i18n/locales/en/common.json 增加同步卡片文案与错误提示(en,含复数)。
apps/desktop/src/renderer/i18n/locales/ja/common.json 增加同步卡片文案与错误提示(ja)。
apps/desktop/src/renderer/i18n/locales/ko/common.json 增加同步卡片文案与错误提示(ko)。
apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx 设置页新增同步卡:状态展示、立即同步、开关与状态订阅。
apps/desktop/src/renderer/components/settings/contacts/ContactsManagerDialog.tsx 管理弹窗头部新增同步状态展示与立即同步入口。
apps/desktop/src/preload/preload.ts 通过 contextBridge 暴露 sync IPC invoke 与 status-changed fan-out。
apps/desktop/src/main/maker-ipc/channels.ts 新增 contacts sync IPC channels 常量。
apps/desktop/src/main/maker-ipc/contacts-ipc.ts 增加 sync IPC handlers、status 广播通道、并对 invoke 加 trusted renderer 校验。
apps/desktop/src/main/maker-ipc/tests/contactsIpc.test.ts 覆盖 sync IPC handler 行为与参数校验。
apps/desktop/src/main/maker-host/contacts-settings-store.ts settings 落盘新增 deviceSyncEnabled,并支持热刷新 invalidateIfChanged。
apps/desktop/src/main/maker-host/contacts-change-events.ts 新增 main 内部“本地通讯录变更”事件(供同步驱动监听)。
apps/desktop/src/main/device-link/index.ts 接入 contacts sync driver:presence/online 变化、relay 入站帧分发、初始化驱动。
apps/desktop/src/main/contacts-sync/crypto.ts X25519 + HKDF + AES-256-GCM 原语实现(含 AAD 绑定)。
apps/desktop/src/main/contacts-sync/wire.ts gzip+AES-GCM 后分片的 wire 格式、帧校验与分片重组解码器。
apps/desktop/src/main/contacts-sync/lanTransport.ts 局域网 UDP multicast 发现 + TCP 直连传输密文帧(失败回退 relay)。
apps/desktop/src/main/contacts-sync/keyStore.ts safeStorage 加密的 owner-scoped 私钥与 TOFU peer 公钥 pin 落盘。
apps/desktop/src/main/contacts-sync/statusStore.ts 最近一次成功同步信息的 owner-scoped 持久化(不含内容)。
apps/desktop/src/main/contacts-sync/statusModel.ts 同步状态机类型与错误码归类(用于 UI 展示)。
apps/desktop/src/main/contacts-sync/sender.ts 出站发送:基于 known clocks 生成增量,优先 LAN 否则 relay,跨 await 校验上下文。
apps/desktop/src/main/contacts-sync/driver.ts 同步驱动总控:启停、owner/generation 隔离、去抖与定时校准、入站解密合并与状态广播。
apps/desktop/src/main/contacts-sync/tests/crypto-wire.test.ts 覆盖加密/解密、分片乱序重组、篡改检测与 wire 校验。
apps/desktop/src/main/contacts-sync/tests/lanTransport.test.ts 覆盖 LAN 直连仅传密文、端点缺失时回退 relay 的行为。
apps/desktop/src/main/contacts-sync/tests/keyStore.test.ts 覆盖密钥加密落盘、TOFU pin 与 fail-closed。
apps/desktop/src/main/contacts-sync/tests/driver.test.ts 覆盖 owner 切换中断发送、避免旧 owner 密文转发等边界。
apps/desktop/forge.config.ts macOS 增加 NSLocalNetworkUsageDescription 以支持局域网直连同步。

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/maker-core/src/contacts/sync/merge.ts Outdated
Comment thread packages/maker-core/src/contacts/sync/merge.ts Outdated
Comment thread packages/maker-core/src/contacts/sync/materialize.ts Outdated
Comment thread packages/maker-core/src/contacts/sync/repository.ts Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 10:56
Comment thread apps/desktop/src/main/contacts-sync/lanTransport.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 41 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/maker-core/src/contacts/sync/materialize.ts:94

  • 这里用 toLocaleLowerCase() 会受系统 locale 影响(例如土耳其语 i/İ 规则),可能导致不同设备在同一输入下算出不同的去重 key,从而选出不同“赢家”,破坏同步状态“确定性/最终收敛”的前提。建议改用 locale 无关的 toLowerCase()(或显式指定固定 locale)。
    apps/desktop/src/main/maker-ipc/contacts-ipc.ts:164
  • 该 handler 在 deps.readDeviceSyncStatus 未提供时仍可能返回 undefined(optional chaining),但 renderer 侧会把返回值当作 ContactsDeviceSyncStatus 使用。建议将 readDeviceSyncStatus 作为必需依赖并直接调用,保持 IPC 契约稳定。
      if (!deps.syncNow) throwIpcError('INTERNAL', 'contacts sync is unavailable');
      try {
        await deps.syncNow();
        return await deps.readDeviceSyncStatus?.();
      } catch (err) {

packages/maker-core/src/contacts/sync/materialize.ts:53

  • preferNewest() 在 stamp 相同时用 localeCompare() 做 tie-break,比较结果可能因运行环境 locale 不同而变化;在冲突场景(同 stamp)下可能导致不同设备选择不同记录作为唯一赢家。为保证跨设备确定性,建议改为按 code point 的稳定比较(< / >)。
    apps/desktop/src/main/maker-ipc/contacts-ipc.ts:155
  • 该 handler 在 deps.readDeviceSyncStatus 未提供时仍可能返回 undefined(optional chaining),但 renderer 侧会把返回值当作 ContactsDeviceSyncStatus 使用,容易触发运行期错误。建议与 setDeviceSyncEnabled 一样把 readDeviceSyncStatus 作为必需依赖并直接调用。

This issue also appears on line 160 of the same file.

      if (!deps.setDeviceSyncEnabled) throwIpcError('INTERNAL', 'contacts sync is unavailable');
      try {
        await deps.setDeviceSyncEnabled(enabled);
        return await deps.readDeviceSyncStatus?.();
      } catch (err) {

Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 11:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8dd46e3ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/maker-core/src/contacts/sync/repository.ts
Comment thread apps/desktop/src/main/contacts-sync/driver.ts
Comment thread apps/desktop/src/main/maker-ipc/contacts-ipc.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 44 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx:153

  • 这里在展示上次成功同步时,会把 lastRoute 为 null 的情况当作 relay(因为条件只检查 time/deviceName,route 通过三元表达式默认落到 relay)。如果历史状态文件来自旧版本或字段缺失,UI 会显示错误的传输路径。建议仅在 lastRoute 有值时才展示 lastSuccess。
        route: t(
          syncStatus.lastRoute === 'lan'
            ? 'settings.contacts.sync.route.lan'
            : 'settings.contacts.sync.route.relay',
        ),

Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 11:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/maker-core/src/contacts/sync/repository.ts:49

  • projection_json 的 shape 校验过于宽松:这里只检查了“是数组且不超上限”,但数组元素可能是 null / 非对象 / 缺少 id。随后 captureContactsSnapshot() 会对 previous.*row.id 访问并构建 Map,这会把“磁盘投影损坏”变成运行期 TypeError,而不是预期的 fail-closed ContactsError。建议至少校验每个条目是对象且包含非空的 id 字符串(更深的字段校验可以后续再加)。
    apps/desktop/src/main/maker-host/contacts-settings-store.ts:6
  • 文件头注释里写的是 <userData>/contacts-settings.json,但实现实际落在 ownerScopedUserDataPath():有 owner 时是 <userData>/owners/<hash>/...,无 owner 时是临时目录下的进程隔离路径。建议更新注释,避免后续排查/安全审计时误判设置的归属与隔离边界。
 *
 * 落盘文件: <userData>/contacts-settings.json
 *   { "enabled": false, "deviceSyncEnabled": false }
 *

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 655ef419d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/maker-core/src/contacts/sync/materialize.ts Outdated
Comment thread packages/maker-core/src/contacts/sync/validation.ts Outdated
Comment thread apps/desktop/src/main/contacts-sync/driver.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@zqchris 👋 这个 PR 还有 3 条 review conversation 没 resolve(packages/maker-core/src/contacts/sync/materialize.ts / packages/maker-core/src/contacts/sync/validation.ts / apps/desktop/src/main/contacts-sync/driver.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

zqchris added 2 commits July 31, 2026 20:35
Signed-off-by: zqchris <chrisz83@gmail.com>

# Conflicts:
#	packages/device-link/src/index.ts
Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 12:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37d8eec57f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/maker-core/src/contacts/sync/repository.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 56 out of 57 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx:152

  • syncSummary 在渲染 lastSuccess 时把 lastRoute 仅按 === 'lan' 分支判断;当 lastRoute 为 null(例如旧状态文件/异常路径未记录 route)时会错误落到 relay 文案,导致 UI 显示与真实传输路径不一致。建议仅在 lastRoute 有值时才展示 lastSuccess,否则回退到“ready”。

Comment thread packages/maker-core/src/contacts/sync/repository.ts Outdated
Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 18:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 860c60db0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/contacts-sync/wire.ts Outdated
Comment thread apps/desktop/src/main/contacts-sync/keyStore.ts
Comment thread apps/desktop/src/main/contacts-sync/wire.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 56 out of 57 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/maker-core/src/contacts/sync/repository.ts:39

  • projection_json 的校验目前只检查“六个字段是数组且长度不超限”,不校验数组元素是否为对象/是否含 id。这样磁盘数据一旦损坏(例如数组里混入 number/string),parseProjection 会通过,但 reconcile()/capture 时会在访问 row.kind / row.id 等字段处抛出运行时异常,导致无法按预期 fail-closed 到 ContactsError。建议在形状校验阶段至少保证每个元素是 record 且包含 string 类型的 id(其余字段可继续保持宽松),确保坏数据会被 parseProjection 拒绝并抛出受控错误。
    apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx:152
  • syncSummary 在渲染 lastSuccess 时对 lastRoute 使用了“非 lan 即 relay”的兜底;但 lastRoute 类型允许为 null(例如旧版本/损坏状态文件),此时会把未知路径错误显示为“加密中转/Encrypted relay”。建议仅在 lastRoute 为 'lan' | 'relay' 时才展示 lastSuccess,否则回退到 ready 文案,避免误导用户。

@MagicLizi

Copy link
Copy Markdown
Contributor

@zqchris 👋 这个 PR 还有 5 条 review conversation 没 resolve(packages/maker-core/src/contacts/sync/repository.ts / apps/desktop/src/main/contacts-sync/wire.ts / apps/desktop/src/main/contacts-sync/keyStore.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 20:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 62 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx:153

  • syncSummary 在渲染 lastSuccess 文案时,把 lastRoute 仅用 === 'lan' ? lan : relay 二分;当 lastRoute 为 null(例如旧版本状态文件、损坏/缺字段的持久化状态)时会被误显示为 relay,从而向用户展示错误的“传输路径”。建议在 lastRoute 为空时不要渲染 lastSuccess(或至少不要填充 route)。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df8e1ddb9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/contacts-sync/keyStore.ts Outdated
Comment thread apps/desktop/src/main/contacts-sync/contactsSyncCodec.ts Outdated
Comment thread apps/desktop/src/main/contacts-sync/contactsSyncCodecWorkerClient.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@zqchris 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/desktop/src/main/contacts-sync/keyStore.ts / apps/desktop/src/main/contacts-sync/contactsSyncCodec.ts / apps/desktop/src/main/contacts-sync/contactsSyncCodecWorkerClient.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/desktop/src/renderer/components/settings/contacts/ContactsSection.tsx:152

  • 当 lastSyncAt/lastSyncDeviceName 存在但 lastRoute 为空(例如旧版本状态文件/读取回退/部分写入)时,这里会默认把 route 当作 relay 展示,可能误导用户。建议只有 lastRoute 为 'lan'|'relay' 时才拼接 lastSuccess,否则回退到 "ready"(或单独的无 route 文案)。
    packages/maker-core/src/contacts/sync/types.ts:9
  • 该目录下的 TypeScript 文件普遍使用单引号(例如 packages/maker-core/src/contacts/types.ts),这里新增文件使用双引号会造成风格不一致,且在启用 lint/prettier 的情况下可能直接触发检查失败。建议统一为单引号。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95c45f9dca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/maker-core/src/contacts/sync/validation.ts
@MagicLizi

Copy link
Copy Markdown
Contributor

@zqchris 👋 这个 PR 还有 1 条 review conversation 没 resolve(packages/maker-core/src/contacts/sync/validation.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: zqchris <chrisz83@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 21:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 64 out of 65 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/desktop/src/main/maker-ipc/contacts-ipc.ts:156

  • CONTACTS_SYNC_STATUS_GET 直接透传 readDeviceSyncStatus() 的异常,会把裸 Error 返回给 renderer,绕过当前文件已有的 rethrowAsIpcError() 统一 [CODE] 协议(其他 handler 已做 try/catch)。建议与 ENABLED_SET/NOW 保持一致:捕获异常并 rethrowAsIpcError(err)。

@MagicLizi

Copy link
Copy Markdown
Contributor

UI 证据提醒(非阻断)

本 PR 修改了 UI 路径(ContactsManagerDialog.tsx / ContactsSection.tsx / contactsService.ts),但 description 未附界面效果证据。建议补充改动后效果截图/录屏,便于确认界面符合 DESIGN.md 设计规范。

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查通过。9700+ 行 E2E 加密联系人同步实现——X25519 ECDH + HKDF + AES-256-GCM 密码学层正确,密钥管理经 safeStorage 加密存储,LAN 传输带 HMAC 挑战响应认证,IPC 全部经过 assertTrustedAppRendererEvent 守卫,输入校验(validation.ts)有完整的边界和约束检查。5 处安全软命中均为测试桩/占位符(安全)。无 P0/P1。

@MagicLizi
MagicLizi merged commit 0759b09 into main Jul 31, 2026
13 checks passed
@MagicLizi
MagicLizi deleted the contacts-device-sync branch July 31, 2026 22:12
@MagicLizi

Copy link
Copy Markdown
Contributor

LAN 上做 E2E 加密同步这套方案扎实——TOFU 密钥信任 + HMAC 挑战响应 + 按 transfer 做 AAD 绑定,攻击面控制得很紧;资源限制(并发连接数/worker 队列/解压上限)也都有,不怕被恶意 peer 打满。

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.

3 participants