CalDAV 云日历复查与修复(2026-10-20)¶
历史记录 / Historical record:本文保留当时的计划、研究或验收证据,不代表 0.11 当前能力全集。现行说明见 文档入口;英文入口见 English overview。
范围:state/cloudCal.ts、核心 CalDAV/ICS、日程编辑门面和严格文件持久化。全部测试使用隔离内存文件与 fake DAV;没有读取实际账号文件、请求学校服务或修改真实云日历。
历史策略确认¶
- MCP 的「OneTHU 日程云同步 CalDAV 方案启动」记录了 2026-09-08 初始方案,但不能据此断言今天仍自动上传课程。
433f84a7明确退役课表自动上云,a0c18677停用作业 DDL 上传。当前继续保持这两个自动上传功能停用。- 旧
buildHwCloudEvents()恒返回空数组,却仍由学习数据订阅触发清除远端 homework。此次去掉隐式清理;显式编辑/删除云事件仍可用,历史云事件不会再因学习数据刷新被自动删除。 - 云端拉取与本地系统日历同步是两条链路。打开日程页/显式同步拉取 CalDAV;系统日历由另一状态层跟随课表、作业和手动日程。
已修复¶
- 初始化和账号隔离:凭据与日历内容分别使用 singleflight 恢复。邮箱只等待
ensureCloudCalCredentialsLoaded(),日历缓存或本地事件损坏不会阻断有效邮箱凭据;配置自身的错误仍明确拒绝。配置通知与内容通知分开;换邮箱、换授权码、断开均通知邮箱消费者。云镜像带邮箱归属,旧未标记缓存不冒充当前账号;账号 epoch 拒绝旧请求回灌。 - 破坏性消费者就绪屏障:
ensureCloudCalMirrorReady()等待写入队列稳定;新账号/无归属缓存还要成功拉取远端,才允许系统日历将它当成完整镜像。读取异常不会返回空成功。 - 并发与冲突:扫描、增删改串行,并发刷新共享同一结果;更新沿用真实资源 href 与原 ETag,新增使用
If-None-Match:*,编辑/删除用If-Match,412 明确要求同步处理。草稿提供旧 ETag 时不能借后台同步后的新 ETag 越过冲突。 - 重复日程资源:镜像按资源 href 保存所有 master/override,而非同 UID 只留一条;编辑主事件保留资源内其他实例。本地同侧编辑按 UID + recurrenceId 更新。跨侧移动重复系列暂不支持,明确拒绝,避免误删整系列。
- 安全移动:
saveCalendarEvent()先持久化目的侧,再删除源侧;写失败保留源,清理失败保留两份并明确提示。Schedule 与插件 cal.edit 共用该入口。 - 真实持久化错误:
clients.ts的三个文件 helper 增加可选strict,日历全部启用。原 helper 吞掉了 Tauri I/O 错误,必须修此层才能保证先写后删。Native NotFound 保持 null,其余错误传播;浏览器 strict 使用独立 localStorage 键,不再空写假成功。其他默认调用行为不变。 - 显式课表导出:先条件写入全部新事件,再清理过期项;新写入失败时不先清空旧课程。源数据缺日期/非法时间报告 skipped,整批拒绝破坏性导出;校园时刻明确使用 Asia/Shanghai,不随设备时区偏移。
- ICS 编解码:补上长行 continuation 前导空格,修字面反斜杠转义;保留 EXDATE/RECURRENCE-ID;无 DTEND 全天事件按一天处理;实例透传自己的提醒提前量。
- 错误响应防空镜像:无效 207 清单和 HTML 假成功不当空日历;DAV 返回跨 origin 地址时拒绝发送 Basic 凭据。配置探测无可用日历不能误报成功。
- 损坏配置的显式恢复:设置页共用的配置/断开入口不再要求日历先成功加载;它们只严格写入或删除凭据,保留全部 cache/local 文件。持久化成功才发布新连接状态,写入或删除失败保留原状态。修复凭据不会绕过日历内容就绪屏障,损坏或 I/O 失败仍阻止系统镜像;慢读取不能复活已替换的旧账号。
验证¶
node tools/cloud-calendar-test.mjs:137 项离线集成,运行真实日历/邮箱状态层、真实 CalDavClient 和真实 ICS,仅替换 I/O 边界。新增覆盖坏 cache/local 仍可收邮件但不能覆盖系统镜像、坏 cfg 拒绝邮箱、实际配置/断开入口恢复、持久化失败与在途旧凭据读取。node tools/mail-state-test.mjs:23 项邮箱状态检查;仅将夹具接口替换为独立凭据加载,原邮箱覆盖保留。- 相同测试在
TZ=America/Los_Angeles下覆盖校园时区口径。 node tools/state-file-strict-test.mjs:11 项,执行实际 helper 声明,覆盖 native 错误传播/NotFound、浏览器持久化与配额错误。env -u CALUSER -u CALPASS node --experimental-strip-types packages/core/test/caldav.smoke.mjs:旧离线冒烟通过,显式禁用联网分支。- desktop TypeScript 检查通过;整仓最终构建由集成者统一执行。
仍需区分的边界¶
- 未对学校服务器、真实邮箱授权码、跨设备同时编辑或真实系统日历做写入验收,以上不是服务在线状态证明。
- ICS 解析器仍是课程日历需要的有限子集:日/周/月基础重复、例外和提醒;不是完整 RFC 5545 客户端。复杂 RRULE、会议参与者、任意 VTIMEZONE/RDATE 等完整往返不在本轮承诺内。
- 同步不提供云端跨请求事务。网络或存储失败可能留下已成功写入的部分;此次保证报告错误、保留可恢复副本,而非声称远端可原子回滚。
- 旧无账号镜像需要一次成功拉取后才参与系统镜像;离线时保持原系统日历,避免错误清空。