资讯动态

Super Productivity 操作日志与同步架构解析:从单客户端操作日志到 SuperSync 的端到端同步管线

发布时间:2026/9/13 8:36:40 来源:尧图企业网站定制
Super Productivity 操作日志与同步架构解析从单客户端操作日志到 SuperSync 的端到端同步管线【免费下载链接】super-productivitySuper Productivity is an advanced todo list app with integrated Timeboxing and time tracking capabilities. It also comes with integrations for Jira, GitLab, GitHub and Open Project.项目地址: https://gitcode.com/GitHub_Trending/su/super-productivity导读本文以 docs/sync-and-op-log/README.md 为骨架系统讲解 Super Productivity 的核心同步基础——Operation Log操作日志它是 SuperSync 与文件类同步提供方WebDAV/Nextcloud、Dropbox、OneDrive、LocalFile共用的单客户端同步管线。读完本文你将理解持久化 NgRx 动作如何被捕获为可持久化操作、向量时钟如何检测因果顺序与并发编辑、v2/v3 文件信封与 SuperSync 操作 API 两种传输形态的差异以及启动时结构筛选快照 重放操作尾部的恢复模型并能在仓库中定位对应的契约文档、实现源码与可执行测试。一、Operation Log一切同步的单一管线Super Productivity 的同步设计有一个核心前提所有同步提供方共享同一条客户端操作日志管线。无论是走 SuperSync 服务器的按操作 API还是走 WebDAV/Nextcloud、Dropbox、OneDrive、LocalFile 的文件信封持久化的 NgRx 动作都遵循同一条路径NgRx reducer 实时更新运行期投影live projection捕获层capture把持久化动作变成可持久化操作durable operation重启恢复时使用经过结构筛选的快照structurally screened snapshot加上保留的操作尾部retained operation tail重建状态向量时钟vector clock负责检测因果顺序causal order与并发编辑concurrent edits。原文档给出的整体架构图如下Persistent NgRx action ┌────────┴────────┐ ▼ ▼ NgRx reducers operation capture │ │ ▼ ▼ runtime projection SUP_OPS (ops, clocks, checkpoints, snapshot) │ ▼ Sync Providers ┌───────────────────┴──────────────────┐ ▼ ▼ SuperSync File providers (ordered op API) (shared v2 or v3 envelopes)这一单客户端同步管线的设计初衷可以从 operation-log-architecture.md 中找到更完整的动机Operation Log 同时服务于**本地持久化A、文件同步B、服务器同步C、校验与自愈D**四个目的。其中事件溯源event sourcing是核心思路——日志记录的是可重放的状态转移而不是逐个持久化 NgRx 模型但它又是有界的操作日志而非自创世起永久的不可变历史启动时加载 state-cache 快照再重放保留的尾部操作载荷是追加写的但投递、应用、拒绝、重试等生命周期元数据可变压缩compaction会删除已被安全快照覆盖的旧操作。二、两种传输形态SuperSync 操作 API 与文件信封2.1 SuperSync按操作传输向量时钟判定并发SuperSync 传输的是单个 Operation而非完整文件带宽开销小因为每个操作都携带向量时钟系统可以在数学上证明两个变更是否并发。例如设备 A 发送更新标题版本 1 → 2设备 B 发现自己也是版本 1则安全应用若设备 B 同时改了内容且自身也在版本 2则双方从同一版本出发各自修改判定为冲突。冲突解析按语义优先级 → 合格的不相交字段自动合并 → 其余确定性 LWWlast-write-wins的顺序执行普通操作冲突不会弹出赢家对话框。2.2 文件提供方v2 整体文件与 v3 拆分格式文件类提供方使用共享的 v2/v3 信封作为通用适配器格式但需要特别强调的是——这是一个公共适配器格式不是公共的物理写入保证Dropbox 与 OneDrive可以强制使用 API 层的 compare-and-swapCAS语义WebDAV/Nextcloud仅在服务器提供强 ETag 时才是原子写弱 ETag 或缺失 ETag 时退化为 best-effort 检查LocalFile同样只有 best-effort 的 read/check/write 竞态保护定位是单写入者/备份用途。从 operation-log-architecture.md 可以补充两种格式的细节默认 v2 格式在单个sync-data.json中保存完整状态/归档基线加一个有界的近期操作缓冲区每次携带操作的上传都会重写整个文件可选的 v3 拆分格式把sync-ops.json作为热点提交点快照/归档文件只在引导、压缩、迁移、强制上传或间隙恢复时才重写。两种格式最终都汇入同一条客户端操作日志管线。三、写入路径与读取路径从动作到可重放操作3.1 写入路径Write Path用户勾选一个复选框这样的普通操作在 operation-log-architecture.md 中被描述为四步ReduceNgRx 同步提交实时状态转移Capture捕获元 reducer 把持久的本地动作标记为 pending或在远程操作应用期间推迟它Persist一个非派发 effect 序列化被捕获的动作、校验每个操作并在操作日志锁下以单次原子事务追加操作 其新向量时钟Schedule sync仅在持久化追加成功之后客户端才更新 pending 状态并请求上传。关键设计是reducer 先于异步追加运行。如果持久化失败实时状态可能领先于持久化日志此时客户端会显示 reload 动作并阻止压缩把这种幻影变更烘焙进快照。实现层面只有显式携带meta.isPersistent: true的动作才进入捕获路径远程/重放动作设置meta.isRemote且绝不会被再次捕获。可执行契约位于 persistent-action.interface.ts、operation-capture.meta-reducer.ts 与 operation-log.effects.ts。3.2 读取路径Read Path / 水合从头重放每一个操作太慢因此使用快照加速加载快照启动时加载最新且有效的 state-cache 快照无快照时走 legacypf数据的 Genesis 迁移重放尾部查询该快照之后的所有操作快进把少量尾部操作应用到快照上应用即达到最新状态水合优化如果刚发生过同步可以直接加载新状态跳过重放。两个额外优化当最后一个可重放操作是SYNC_IMPORT/BACKUP_IMPORT/REPAIR且重放范围内没有待处理的 reducer 工作水合器直接校验并加载其完整状态当尾部重放超过 10 个操作时会保存新的快照避免下次启动重复重放。该流程的权威实现位于 operation-log-hydrator.service.ts 与 operation-log-recovery.service.ts。四、远程应用与崩溃安全SUP_OPS 的持久化存储本地持久化数据库SUP_OPS不止一张追加写表其 store 与索引由 OperationLogStoreService 权威定义包括操作行含可变的投递/应用元数据、state cache 基线含覆盖的本地序列、向量时钟、schema 版本、实体前沿、时钟/客户端/元数据行以及位于 NgRx 之外的 young/old 归档 store。操作信封的确切形态由 sp/sync-core 的 operation.types.ts 定义应用侧在 operation.types.ts 中收窄。下载的远程操作使用可持久化的状态迁移保证崩溃后 reducer 状态、归档 IndexedDB 副作用、向量时钟与服务器游标不会彼此不一致pending—— 远程操作已存储但尚无 reducer 提交检查点archive_pending—— reducer 已提交且操作向量时钟已原子合并但归档副作用未完成failed—— 归档副作用失败retryCount只计入该行不消耗批次内后续行的重试预算applied—— reducer 与归档工作全部完成。批量重放按操作隔离转换/reducer 异常reducer 成功的子序列被打检查点并接收归档副作用reducer 失败的远程行以rejectedAt加reducerRejectedAt在同一事务内标记终结。水合会重试archive_pending/failed行禁用 reducer 派发普通同步在不完整行存在时拒绝下载、上传或推进游标。五、向量时钟因果顺序与并发检测的基石5.1 核心类型与基本操作向量时钟追踪因果性——这个客户端是否知道那个操作——而非会漂移的墙上时钟。核心类型是{ [clientId]: counter }映射{A: 5, B: 3}表示该状态包含 A 的前 5 个操作与 B 的前 3 个操作。MAX_VECTOR_CLOCK_SIZE 20packages/sync-core/src/vector-clock.ts6 字符客户端 ID 下 20 条目约 333 字节带宽开销可忽略用户需要 21 个以上唯一客户端 ID 才会触发剪枝对个人生产力应用而言极罕见。对比、合并、剪枝三个通用操作在sp/sync-core的 vector-clock.ts 中实现客户端与服务器共用初始化与自增两个客户端专属操作位于 src/app/core/util/vector-clock.ts并包一层空值处理与日志。比较结果可能是EQUAL | LESS_THAN | GREATER_THAN | CONCURRENT之一缺失键按零处理合并产生支配两侧的新时钟自增在逼近MAX_SAFE_INTEGER时抛错唯一恢复手段是SYNC_IMPORT重置时钟。5.2 时钟的存放位置每操作时钟每个 Operation 携带vectorClock字段即创建该操作时的全局时钟状态——这是因果追踪的主要机制全局时钟 store存于 IndexedDBSUP_OPS数据库的vector_clockobject store作为客户端当前因果知识的唯一权威来源。本地操作捕获时通过单次 IndexedDB 事务appendWithVectorClockOverwrite与操作写入原子更新远程合并路径mergeRemoteOpClocks同样以单次读-合并-写事务执行且读取的是事务内的新鲜持久化时钟而非 per-tab 缓存避免并发标签页因陈旧读取丢失条目快照时钟state_cache保存压缩时刻的向量时钟作为未被修改实体的基线实体前沿entity frontier由 VectorClockService 的getEntityFrontier()按需计算扫描快照后的操作得到用于细粒度冲突检测。5.3 正常生命周期客户端不剪枝服务器比较后再剪枝这是整套设计的关键非对称性本地创建operation-log.effects.ts读取全局时钟 → 自增本客户端计数 → 操作携带完整未剪枝时钟→appendWithVectorClockOverwrite单事务写操作并更新全局时钟。正常操作在捕获期间绝不客户端剪枝上传服务器sync.service.ts的processOperation校验服务把时钟规范化DoS 上限 2.5×MAX 50 条目超限整体拒绝而非剪枝detectConflict()使用完整未剪枝时钟比较被接受后才limitVectorClockSize()剪枝到 MAX 再入库保留上传客户端与最新的因果全量状态作者其他客户端下载mergeRemoteOpClocks每个下载操作的时钟并入本地全局时钟对全量状态操作SYNC_IMPORT/BACKUP_IMPORT/REPAIR全局时钟替换而非合并为导入时钟后再并入其余操作非全量下载则保留所有既有条目。先比较后剪枝是经历真实故障换来的教训详见 vector-clocks.md 第 11 节Riak #613 的兄弟爆炸与 Super Productivity 2026 年 2 月MAX10时的无限拒绝循环合并 11 条目 → 服务器剪到 10 → 非共享键强制 CONCURRENT → 拒绝 → 再合并都源于剪枝先于比较。当前实现把 MAX 定为 20保留集当前客户端 最新全量状态作者只在OperationLogStoreService.pruneClockForStorage内维护其余任何src/app位置的limitVectorClockSize导入都会触发 lint 失败。5.4 冲突检测与解析流程服务器对每个实体做两次独立索引查找标量findFirst 对entity_ids的原始 SQLMATERIALIZEDCTE 取MAX(server_seq)取serverSeq更高者刻意不做成单条合并过滤——2026-07-20 那次把两个索引查询合并成OR过滤导致 47 个后端卡死、最长 75 分钟的同步宕机正是教训。比较结果分五类GREATER_THAN接受、同客户端EQUAL接受同一操作重试、异客户端EQUAL拒绝可疑时钟复用、CONCURRENT拒绝真冲突、LESS_THAN拒绝已被超越。客户端收到拒绝后SupersededOperationResolverService 合并全局时钟、所有被超越操作时钟、快照时钟与强制下载的额外时钟mergeAndIncrementClocks()生成不剪枝的支配时钟创建新 LWW Update 操作重新上传服务器再次比较此时必然 GREATER_THAN后剪枝入库。安全网是 RejectedOpsHandlerService 的逐实体连续失败计数超过MAX_CONCURRENT_RESOLUTION_ATTEMPTS3即永久拒绝。双客户端并发修改的完整分步推演见 vector-clocks.md 第 8 节。六、SECTION 冲突重放契约窄化的语义例外section-conflict-replay.md 定义了普通实体 LWW 不足之处的窄化例外SECTION 动作在 section 与其 Project/Tag 工作上下文之间编码有序关系把被拒绝的移动/移除/重排替换成单一实体快照会丢失 reducer 语义——任务可能同时留在两个容器、从两者都消失、或各客户端收敛出不同顺序。因此当服务器拒绝并发本地操作时resolver 可以重放被拒绝的 SECTION 意图而非折叠成通用实体快照。只有SECTION_UPDATE_ORDER、SECTION_ADD_TASK、SECTION_REMOVE_TASK三个动作族有资格且必须同时满足被拒操作有既有实体前沿、恰好一个保留操作匹配受影响实体/时钟前沿、该保留行是已应用且已同步的非拒绝远程操作、areCommutingSectionOperations()认可该操作对、操作元数据与决策所用动作载荷完全一致。被认可的交叉严格限定为两种同任务从移动源 section 的移动移除以及触碰某个有序 section 的放置/移除交叉的 section 顺序更新。重放被投影到一个稳定的 NgRx 快照上四种结果replay用当前顺序与锚点创建替换操作、work-context-state创建维持工作上下文顺序所需的精确补偿、superseded当前持久化状态已使意图过时拒绝旧前驱且不替换、blocked无法安全表示保留通用 LWW 回退。替换与补偿操作与对旧前驱的拒绝在同一个操作日志事务中追加崩溃不会只暴露恢复的一半。仓库还要求兼容 v18.4.0–v18.4.3 发布窗口的客户端它们理解 schema-4 的 SECTION 移除但忽略工作上下文锚点字段因此语义移除在需要时会配对完整的 Project/Tag LWW 替换。聚焦单元测试运行npm run test:file src/app/op-log/sync/superseded-operation-resolver.service.spec.ts真实客户端收敛场景见 supersync-section-convergence.spec.ts。七、冲突日志与不相交字段自动合并conflict-journal-and-review.md 说明了 LWW 自动解析如何被记录、何时两个并发编辑都被保留以及用户如何复盘/sync-conflicts页面、banner、badge。7.1 冲突日志当前为休眠状态日志条目写入独立的 IndexedDB 数据库SUP_CONFLICT_JOURNAL刻意与SUP_OPS分离杜绝影响操作日志 schema/版本化。契约是只观察记录绝不反向影响 LWW 选了谁且每次日志写入都吞掉自身错误操作日志与日志写入非原子——崩溃最多丢日志条目绝不丢操作。条目设备本地保存、永不参与同步会复活被丢弃的字段值在完整数据集替换时清除BackupService.importCompleteBackup是唯一收口。保留规则是 14 天JOURNAL_RETENTION_DAYS与最新 200 条JOURNAL_MAX_ENTRIES先到先剪外加软上限 220 条后才触发会话中剪枝。安全边界该库是普通明文设备本地 IndexedDB不继承传输加密或 SuperSync E2EE字段值原样存储意味着ISSUE_PROVIDER冲突可能把 API 密钥、令牌、client secret 持久化下来——这正是当前主处理路径设置disableConflictJournal: true的原因。7.2 不相交字段自动合并当两个客户端同时编辑同一实体但不同非噪声字段时整体实体 LWW 会丢弃一方真实编辑因此改为合成一条合并 UPDATE 操作。合格条件包括任一方无 DELETE 且不是归档计划任一方不含多实体操作任一方无 opaque 操作双方至少各改了一个真实字段双方非噪声改动字段集不相交该实体在本批次中只有一个冲突实体类型有RECREATE_FALLBACKTASK/PROJECT/TAG/SIMPLE_COUNTER。收敛契约是无论哪一方执行合并双方必须合成逐字节一致的合并 delta。delta 只由双方操作推导绝不取自任一方当前实体快照快照会拖入双方都没碰的字段在交错同步竞态下可永久发散结果恰好是一条携带扁平部分 delta 的新 UPDATE 操作如同普通编辑一样叠在双方历史上无历史回卷扁平载荷使extractUpdateChanges返回{}因此合并操作自身永远不能再成为合并候选不级联、不再合并。7.3 复盘 UI/sync-conflicts提供两个视图未复查 / 历史每个条目的动作是KEEP确认自动解析或FLIP通过派发普通实体更新动作重新应用被丢弃一侧——被捕获为可同步操作传播到各处本质是在当前状态之上的全新编辑而非历史回卷。FLIP 前有陈旧性守卫其能力刻意收窄仅 TASK/PROJECT/NOTE/TAG 这类可用普通{id, changes}更新的类型不含 delete-lost/delete-wins也不含关系承载字段projectId、parentId、subTaskIds、tagIds等与日程/提醒字段这些由元 reducer 或专门流程维护一致性。被翻转的标题以isIgnoreShortSyntax: true派发避免#tag/project/schedule短语法把日志里的字面值重新解析成交叉实体变更。八、贡献者同步模型写 effect 前必须理解的不变式contributor-sync-model.md 把几乎所有同步正确性规则归结为一条不变式每个可重放原子转移通常是单个持久化动作 恰好一个操作。重放与远程操作绝不能重新触发 effect。reducer必须为远程/重放操作运行这是状态重建的方式effect绝不能——UI 副作用snack、声音、导航在发起端已经发生且工作流主动发出的每个持久化转移都已有自己的日志条目。重放时重跑 effect 会重复副作用并产生与同步冲突的幻影操作。该不变式落在三个边界上动作边界effect 只注入LOCAL_ACTIONS标准动作流过滤掉meta.isRemote见 local-actions.token.ts唯一合法例外是处理全量捕获的operation-log.effects.ts用ALL_ACTIONS自行处理isRemote。由local-rules/no-actions-in-effectslint 强制写错即报错选择器边界响应 store 状态而非特定动作的 effect 会绕过边界 1在包括水合与同步重放在内的每次状态变化时触发。skipDuringSyncWindow()适用于可安全重试的级别/重复型源故意丢发射waitForSyncWindow()适用于稀疏或边沿触发、丢了就丢了的源先捕获该边沿所需状态再在窗口关闭后处理。后者是 30 秒后 fail-open 的不是硬互斥边界skipDuringSyncWindow()还会额外检查初始同步门。由local-rules/require-hydration-guard强制原子性规则涉及多实体的原子可重放转移必须是一次 reducer 过放进 src/app/root-store/meta/task-shared-meta-reducers/ 的元 reducer成为一个操作而不是 effect 里派发的一串后续动作扇出扇出会为一个原子转移发出 N 个操作且重放时重跑。local-rules/no-multi-entity-effectwarn启发式捕获数组字面量扇出形态。循环 50 次派发后补一个宏任务让位await new Promise((r) setTimeout(r, 0))保护捕获顺序。同一文档还记录了清空字段陷阱#9776JSON.stringify会从操作载荷中丢弃undefined键因此绝不能依赖changes: { someField: undefined }抵达另一台设备。安全模式按优先级是reducer/元 reducer 内基于专用动作载荷只带 id设置undefined从解构载荷字段重建changes对泛型UpdateT用clearedFieldsProps()带外列出被清空键cleared-update-fields.ts。禁止发明带内哨兵值null、0、标记字符串旧客户端会把哨兵持久化并通过 typia 校验失败。该文档还包含sync-epoch 栅栏#9074同步周期横跨多个await破坏性配置变更切换 provider/账号、移动文件夹、开关加密/改密码可能落在任一间隙。SyncProviderManager.syncEpoch是单调计数器在每次此类变更完成后以及runWithSyncBlocked入口递增每个周期在同一同步块内读取 (provider, epoch) 对并作为fenceEpoch传递provider I/O 统一在getOperationSyncCapable(provider, { fenceEpoch })处逐调用断言。断言失败抛SyncEpochChangedError在一切入口处按良性中止处理无错误 snackUNKNOWN_OR_CHANGED——每个中止点设计上都等价于崩溃延迟 ack 会重新上传落后游标去重后重新下载。九、加密边界SuperSync 的端到端加密supersync-encryption-architecture.md 说明 SuperSync 使用AES-256-GCMArgon2id密钥派生实现端到端加密加解密全部在客户端完成。输出格式为salt(16B) || iv(12B) || (ciphertext authTag)的 base64会话稳定 salt 摊薄 Argon2id 的昂贵派生成本GCM 安全性依赖每个载荷的新鲜随机 IV 唯一。安全属性包括保密性服务器无法读取操作载荷、载荷完整性GCM auth tag 检测篡改、密钥安全Argon2id 抗 GPU 暴力破解、随机 IV 唯一性不提供前向保密。需要特别强调的完整性范围只有op.payload被加密并受 auth tag 保护其余字段actionType、opType、entityType、entityId(s)、vectorClock、timestamp、schemaVersion、syncImportReason乃至isPayloadEncrypted标志本身都以明文传输且未作为 AAD 绑定——因此一个恶意/被攻破的服务器或 TLS MITM 可篡改它们。客户端以纵深防御在四个向量上 fail-closed明文注入降级当配置强制加密时assertOpsEncryptedWhenExpected拒绝任何入站明文操作下载 随包上传LWWentityId重定向适配器型 LWW 更新中payload.id与op.entityId不一致即拒绝verify-decrypted-op-integrity.ts项目移动足迹注入加密的 TASK 项目移动载荷携带projectMoveSubTaskIds时要求明文op.entityIds与已认证集合{op.entityId} ∪ projectMoveSubTaskIds精确相等全量状态opType提升解密被标记为 SYNC_IMPORT/BACKUP_IMPORT/REPAIR 的操作后结构校验已认证载荷是完整应用数据元数据才可将其提升到loadAllData。把完整信封作为 GCM AAD 绑定、并加单调加密下限防降级是后续 GHSA-8pxh-mgc7-gp3g 追踪的持久修复方向。加密密码/密钥只存于 provider 私密配置绝不进入同步状态或发送给服务器意图位镜像到globalConfig.sync.isEncryptionEnabled以便管线 fail-closed但远程值非权威——水合时重新应用设备本地值。初始设置时客户端先探测服务器downloadOps(0, undefined, 1)服务器已有加密操作则显示输入密码为空/未加密则显示创建密码避免第二台客户端加入时出现双重弹窗。十、本地恢复点抵御数据全没了故障local-recovery-points.md 保护托管 SuperSync 用户一台设备发出的全量状态操作SYNC_IMPORT/BACKUP_IMPORT/REPAIR会传播到所有设备并替换其本地副本而强制 E2EE 下服务器无法重放历史恢复必须在仍持有数据的设备上就地完成。设计是每个设备在任何全量状态替换之前把完整状态捕获进本地恢复环IndexedDBimport_backupstore环大小 3新写同事务删除旧快照并在 Settings → Sync Backup 提供备份列表供浏览与恢复。元数据携带reasonREMOTE_IMPORT/FORCE_DOWNLOAD/LOCAL_IMPORT与taskCountQuotaExceededError时退化为保留最新的REMOTE_IMPORT/FORCE_DOWNLOAD快照并重试一次最新的REMOTE_IMPORT/FORCE_DOWNLOAD条目不会被恢复操作轮换出去三次错误恢复也不能挤掉丢失前快照#10003。捕获失败即中止替换与强制下载同契约已在本地日志中的全量状态操作属于重复投递从 seq 0 强制下载#9975不替换状态也不捕获——否则三次重投递会用丢失后的状态填满环。捕获触发点包括op-log 同步期间的远程全量状态操作REMOTE_IMPORT、Use Server Data/强制下载FORCE_DOWNLOAD、JSON 导入/本地备份恢复/撤销LOCAL_IMPORT。备份列表还聚合 Electron 备份文件与 Android/iOS 原生备份槽恢复走既有 per-platform 加载 importCompleteBackup其自身先捕获LOCAL_IMPORT环条目。远程全量应用后若传入状态任务数不足所捕获快照的一半显示可关闭的收缩 banner链接到备份列表。该特性目标仅限托管 SuperSyncE2EE 下服务器无法归还历史文件类提供方WebDAV、Dropbox、LocalFile的快照水合走不同路径不在范围内。验证链路包括supersync-local-recovery-point.spec.ts双客户端 E2EB 用 1 个任务替换服务器A 收到 banner 并从环恢复自己的 3 个任务B 再接收回来。十一、持久化模型字段如何新增字段而不破坏既有安装persisted-model-fields.md 记录了 AGENTS.md 同步规则 11 背后的完整故障分析持久化模型上的新 REQUIRED 字段会破坏每个既有安装——应写成可选?加运行时默认值。用户磁盘上的既有数据没有新字段typia 校验会在水合时拒绝存储快照。TypeScript 不会警告你required 字段直到你把它加进DEFAULT_*常量才报错之后编译干净——但每个既有安装仍在用自己的旧快照校验失败绿构建不是车队存活的证据。也不能假设存在通用自愈loadAllData只对 21 个globalConfigsection 中的 9 个做按 section 默认合并其余顶层展开后整段赢实体切片只有 auto-fix-typia-errors.ts 的通用强制转换缺 boolean → false、nullable → null该文件的全量globalConfig.*默认化只在用户可见的dataRepair流程运行。新增自愈需要在 auto-fix-typia-errors.ts 加按类型分支不要加进recreate-fallback.const.ts那会顺带把该类型选入 SPAP-14 不相交字段自动合并是同步行为变更。故障是潜伏的水合信任 schema 版本匹配的快照无效模型静默上船直到某次无关 schema bump 把存量数据拖上迁移/校验路径才爆炸#8965 于 2026 年 1 月上船数月后在 v18.15.0 表现为启动即空 store。防护是 frozen-state.spec.ts校验当前模型与冻结的历史状态形状若失败改模型绝不改 fixture。十二、包边界与隐私边界依赖方向即架构package-boundaries.md 定义了可复用同步逻辑如何保持框架无关。允许的依赖方向是src/app - sp/sync-providers - sp/sync-core src/app - sp/sync-core packages/super-sync-server - sp/sync-core - sp/shared-schema规则要点sp/sync-core不得导入 Angular、NgRx、src/app、sp/shared-schema或任何 provider 代码它拥有通用同步引擎原语操作类型、向量时钟比较/合并/剪枝、纯冲突/导入过滤/上传下载/重放/压缩/前缀助手、实体注册表契约、SyncLogger端口sp/sync-providers只能导入sp/sync-core公开导出拥有 Dropbox、OneDrive、WebDAV、Nextcloud、SuperSync、LocalFile 六个 provider 类与文件信封类型app 负责 Angular 依赖注入、NgRx、Electron/Capacitor 桥、OAuth 路由与领域接线sp/shared-schema拥有 app 与 server 共享的 schema 契约与校验器不依赖sp/sync-coresp/super-sync-server依赖 shared-schemaHTTP 契约与 sync-core向量时钟算法。消费者只能从包 barrel 导入如import { compareVectorClocks } from sp/sync-core、import { Dropbox } from sp/sync-providers/dropbox根 barrel 已移除sp/sync-providers必须走聚焦子路径。验证命令是npm run lint、npm run sync-core:build、npm run sync-providers:build、npm run packages:test。隐私边界方面包内日志必须走SyncLogger与安全的结构化元数据ID、计数、动作字符串、实体类型、provider ID、错误名/码可接受URL 元数据只有在调用方剥离查询串、片段、凭据、令牌、原始响应体与用户提供的路径段后才可接受完整实体、操作载荷、任务标题、笔记文本、原始 provider 响应、凭据、头、加密材料必须留在可导出日志之外。十三、故障诊断与严重性分诊13.1InvalidFilePrefixError解码diagnosing-invalid-file-prefix.md 说明下载的同步文件必须以头部pf_[C][E]modelVersion__C 压缩E 加密开头否则客户端抛出InvalidFilePrefixError并在日志导出中记录三个字段回答一个单轮问题是坏的响应服务器/代理还是坏的存储文件字段只是形状绝不包含文件字节字段值解读prefixAt-1头部整体丢失只丢第一字节也读作-1prefixAt 0头部存在但损坏或被前置垃圾推到该偏移headShapemarkup坏的响应代理或登录页 HTML、WebDAV multistatusheadShapebase64与自有密文/gzip 体缺头部一致——存储文件问题非铁证需结合inputLength与 providerheadShapejson歧义加密与压缩默认都关闭时未加密存储体本就是原始 JSON需结合上报者的同步设置headShapeother无法识别或过短Unauthorized、nginx检查inputLengthheadShape无法区分头部被剥离与更大片段都读作base64。恢复上SyncWrapperService对损坏远程文件弹出带 force-overwrite 动作的 snack——但markup除外它指向坏的响应而非坏的存储文件此时提示可能的服务器/代理/登录原因且不提供覆盖动作因为对健康远程文件强制上传以修复瞬时响应会丢失其他设备的数据。若 markup 真是存储文件如 WebDAV 持久化了错误页手动逃生口是 Sync 设置 → Force overwrite remoteDialogSyncCfgComponent 的对应实现。13.2 同步严重性分诊sync-severity-triage.md 是判断同步 bug 是否真实、有多严重的分诊规则其中几条来自真实教训master 会交付给真实用户每个 master 推送自动发布到 Play 内部轨道测试者手机数分钟内自动更新、ghcr.io/super-productivity/supersync:latest就是 masterself-hosterdocker compose pull即运行 master HEAD、Snapedge也由每个 master 推送发布绝不从日期或最新 tag 推断已发布要用git merge-base --is-ancestor commit vtag/git tag --contains commit证明——tags 从时间点切出同步功能常在 tag 之后落地如 #8874 的不相交字段合并落在 v18.14.0 打 tag 约 24 小时后未进入任何发布恢复发布行为不等于安全发布行为本身可能就是 bug#9061 正是因此冻结不相交合并静默重新武装了已发布的数据丢失 #9095用户会以非技术词汇报告同步 bugsync/op-log/conflict关键词抓取会少算约 50 倍应搜索lost, disappeared, gone, missing, duplicate, reverted, old version, overwritten, reset, not syncing等实际用语审计生成的发现是低精度而非低收益约 89% 的同步修复修复的是发布中存在问题的代码而约 97% 的自报同步问题没有复现步骤2026-07 测量——不能把未复现的发现当猜测关闭也不能盲目修复修复必须携带没有它就失败的测试。十四、演进中的活动计划原生 SQLite 迁移sqlite-migration.md 记录了与 #7892/#7931 相关的原生 SQLite 工作状态2026 年 7 月基础已实现并在 CI 测试原生上线未接线IndexedDB 仍是所有平台的活动操作日志后端。动机是 Capacitor Android 上关键 op-log 状态存在于 WebView IndexedDBSUP_OPSWebView 存储被驱逐时会丢失目标是把该库迁到原生 iOS/Android 的 app 私有 SQLite同时保留操作日志的原子性与恢复行为。这不是全局存储重写web/PWA 留在 IndexedDBElectron 保留现有持久化与轮换备份且任何原生后端在真机迁移与回滚被证明前不得成为默认。已落地的基础包括OpLogDbAdapter/OpLogTx后端中立契约、IndexedDbOpLogAdapter生产后端、实现最小SqliteDb端口的SqliteOpLogAdapter内存翻译测试 真实sql.js契约 store 级集成、共享同一物理连接的适配器共享 FIFO 队列防重叠BEGIN#8746、migrateOpLogBackend()复制所有 store 并在提交前校验操作数/最后序列/向量时钟。未接线部分包括无原生 SQLite 插件、工厂仍返回 IndexedDB 适配器、无启动触发、无特性开关。存储契约seq 为正单调主键、appendWithVectorClockOverwrite原子写操作时钟、破坏性状态替换原子性、事务仅在回调 resolve 时提交等七条与迁移安全契约原生特性开关默认关、静默所有写者、目标为空才运行、保留主键含序列间隙、校验后再提交、标记在验证提交后写、IndexedDB 源保留至少一个发布版本、绝不合并两个非空后端是后续滚动的门槛。十五、如何继续深入文档路由与可执行索引原 README 的核心价值之一是按需求路由到正确文档全部索引如下路径均已转换为仓库根目录相对路径你想……读什么五分钟建立全系统心智模型sync-architecture.html —— 独立维护者现场指南浏览器打开本地文件正确编写 effect/reducer/批量派发contributor-sync-model.md —— 唯一不变式、drop-vs-defer 选择器规则与 lint 边界对比 SuperSync 与文件 v2/v3sync-architecture.html 的 transports 小节追踪远程应用、冲突或重启恢复sync-architecture.html 的 remote apply / causality / restart 小节修改 SECTION 冲突/恢复行为section-conflict-replay.md为 SuperSync 场景找可执行覆盖supersync-scenarios.md —— 场景到测试的索引研究被否决的替代方案或跨版本策略operation-log-architecture.md —— 深层理由与历史含规范的 A.7.11 schema-bump 策略从日志导出解码InvalidFilePrefixErrordiagnosing-invalid-file-prefix.md ——headShape/prefixAt解码表#9627判断同步 bug 是否真实、多严重sync-severity-triage.md参考文档按状态分Overviewsync-architecture.html、Contractcontributor-sync-model.md、section-conflict-replay.md、package-boundaries.md、conflict-journal-and-review.md、persisted-model-fields.md、local-recovery-points.md、vector-clocks.md、supersync-encryption-architecture.md、Mixedoperation-log-architecture.md、Triagediagnosing-invalid-file-prefix.md、sync-severity-triage.md。相关位置还包括packages/super-sync-server/docs/architecture.mdSuperSync 服务器专用架构参考、packages/super-sync-server/服务器实现、ARCHITECTURE-DECISIONS.md承重产品/数据决策。已退役的图表文件名保留为小型转发桩operation-rules.md也是兼容性指针不是当前行为或状态的独立来源。结语Super Productivity 的同步系统以单客户端操作日志管线统一了 SuperSync 操作 API 与文件信封两类传输写入路径把持久化 NgRx 动作原子捕获为携带向量时钟的操作读取路径用筛选快照 尾部重放完成恢复服务器在冲突比较之后才剪枝时钟客户端以LOCAL_ACTIONS、同步窗口守卫与元 reducer 三个边界守住一个原子转移 一个操作的贡献者不变式。SECTION 语义重放、不相交字段自动合并、E2EE 的四个 fail-closed 向量、本地恢复环与 sync-epoch 栅栏等设计则分别回答了语义冲突如何恢复并发编辑如何保留双方加密边界如何定义数据丢失如何就地回滚周期穿越配置变更如何防串这些具体问题。对于希望深入实现细节的读者最可靠的路径是沿 supersync-scenarios.md 的场景索引找到对应可执行测试并以 sync-architecture.html 作为全系统心智模型的起点。【免费下载链接】super-productivitySuper Productivity is an advanced todo list app with integrated Timeboxing and time tracking capabilities. It also comes with integrations for Jira, GitLab, GitHub and Open Project.项目地址: https://gitcode.com/GitHub_Trending/su/super-productivity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价