# 已确认需求

2026-10-11 更新。本页是目标需求与验收标准；当前实现与它的差距记录在仓库 `docs/IMPLEMENTATION_AUDIT.md`，README 只描述当前行为。项目未发布，格式和 API 可为满足本页需求而改变。

## 术语

- **仓库**：一组共享同一数据的文件与历史。
- **设备**：持有仓库密钥、能读写明文的客户端。同一用户的多台设备同权。
- **存放点**：只存取不透明密文对象的位置，如 S3 兼容对象存储、本机目录、被 iCloud/OneDrive/Dropbox 等同步的文件夹。存放点不持有密钥。
- **文件**：有稳定身份的对象，路径是它的属性。目录由路径前缀导出，参照 S3 的键模型，不是独立对象。

## 加密

文件内容、文件名和敏感元数据在离开设备前加密；存放点和网络上只有密文。读取验证完整性，错误密码、篡改和损坏明确失败。密钥和明文不进入日志或存放点。

## 威胁模型

- 存放点和网络可以看到对象大小、数量、读写时间和设备数量，看不到内容、名字和目录结构。
- 存放点可以丢失数据或拒绝服务，由多副本而不是密码学保证可用性。
- 存放点回放旧状态时设备不倒退：设备记住已见过的提交，只接受前进；多个来源交叉核对。
- 撤销设备不能撤回它已获得的内容；撤销后它发布的任何记录都被拒绝。
- 存放点凭据（云盘账号、S3 密钥）的撤销由用户负责；持有凭据的被撤销设备仍能下载密文，但读不了新纪元内容。

## 共享索引

每台设备保留完整共享索引，同步后收敛一致，离线时保留最后已知状态。索引包含：当前文件、每个文件的全部未退休版本（元数据与内容引用）、删除状态、冲突状态、判断因果先后所需的信息、设备成员记录（名字、公钥、角色、已同步到的提交位置及其时间）、密钥纪元、仓库保留策略和退休记录。索引只含元数据和引用，不含正文。

## 正文同步与设备保留

正文默认随索引同步到每台设备。每台设备的角色：

- **全量**：保留所有未退休版本的正文（默认）。
- **部分**：只保留指定目录和读取过的内容，其余按需获取；适用于手机等空间受限设备，也用于把不常用的文件移出本机硬盘。

读取时本机缺少正文，就从任何持有该正文的设备或存放点获取；小文件整体获取，大文件只获取读取范围涉及的数据块，由系统自动选择。所有接入方式（含挂载）在运行时自动连接已配置的存放点与对端完成获取，不要求用户另开同步进程。全量设备不会为腾空间自动丢弃未退休正文。

部分设备有本机正文预算。超出预算时自动丢弃已确认存在于至少一台全量设备或存放点的正文，固定目录和最近读取的优先保留；本机写入在得到确认前不丢弃，打开句柄引用的正文不丢弃。丢弃只影响本机缓存，不是同步删除。

承诺：**任何未退休版本的正文存在于每台已完成同步的全量设备和每个存放点。**

### 可安全移除

每个版本可查到存在于哪些设备和存放点，由成员记录中的角色和已同步位置推导。仓库能列出"仅本机持有"的版本；列表为空时该设备可以安全移除。追赶中的全量设备在追上之前按部分设备对待。

### 首次同步

新加入的全量设备先获得索引，再获取正文，新版本优先；传输可中断续传。

## 存放点

仓库可以配置零个或多个存放点，承载索引和密文正文。存放点只需要：写入不可变对象、按名读取（支持范围读取更佳）、按前缀列举、删除。不要求锁、原子覆盖、条件更新或即时可见；对象内容寻址、只增不改，每台设备发布自己的提交头，其他设备按因果链合并。不完整或尚未出现的对象视为"未到达"而不是损坏。

同一仓库的设备可以同时使用 P2P 直连和存放点；两台设备不同时在线时，通过存放点交换。

存放点分两类接法：**文件夹**——只读写本机目录，由 iCloud/OneDrive/Dropbox 等客户端搬运，零授权但只在装有该客户端的电脑可用；**API**——直接通过 HTTP 存取，需要凭据，手机、浏览器和服务器都能用。实现顺序：S3 兼容与本机目录（含云盘同步文件夹）；其次 WebDAV（覆盖自建与多家云盘）；OneDrive/Dropbox/Google Drive 私有 API 只在做手机端时加入。

对所有存放点的要求：

- 远端对象打包，减少对象数量；远端 pack 目标比本机小（4–8MiB 量级），使被整体下载的 pack 和范围读取都划算。
- 对象名含长度或哈希；读到不一致视为未到达并重试，不报损坏。
- 退休生效后，存放点对象再延迟一段短时间回收，应对最终一致；内容寻址使误删只需重传。
- 范围读取可用时按块获取；不可用时按对象整体获取。

## 文件元数据

修改时间是承诺同步的元数据。可执行位等其他属性尽力保留：索引始终记录，API 总能读到，挂载文件系统未必能呈现。不承诺权限、所有者、xattr、ACL、硬链接。

## 版本、历史、垃圾箱与冲突

每个文件的版本构成因果链，删除是一个 tombstone 版本，移动和重命名是改变路径属性的版本，不产生历史或垃圾箱条目；并发的修改跟着文件身份走，不因对方移动而在旧路径复活。目录随文件存在，删除目录即删除其下每个文件；空目录不参与同步，由接入层本地维护。

历史、垃圾箱和冲突是同一条版本链的三种视图，都在共享索引中，对所有设备可见：

- **历史**：文件的旧版本。
- **垃圾箱**：当前版本为 tombstone 的文件，按前缀折叠显示，可整个前缀恢复。
- **冲突**：同一文件有多个无因果先后的 head。

每个版本记录来源设备和时间。

### 当前版本

有因果先后时采用后续版本。真正并发时：内容版本胜过删除；其余取发布时间较晚者，相同再按变更标识。规则只决定目录中展示哪个版本，所有 head 都保留。

不同文件并发争同一路径（并发新建同名文件、移动到已有路径）按该路径的冲突处理：路径呈现一个版本，其余是该路径的冲突 head，解决时保留一个或改名。键模型允许 `a` 与 `a/b` 并存；挂载呈现时目录优先，文件 `a` 通过冲突视图可见。

### 冲突的呈现与解决

目录和挂载只呈现当前版本，不生成冲突副本文件。`list`/`head`、CLI、Web 显示冲突数量及各 head 的来源设备和时间；挂载提供只读虚拟目录查看各 head。

冲突在某个变更以全部 head 为父时结束。显式解决选择一个 head 而不改内容；设备在已看到全部 head 的情况下对该文件写入，新变更自动以全部 head 为父，冲突随之解决。落败的 head 成为历史版本。

### 恢复

恢复历史版本或从垃圾箱取回，发布一个内容引用指向旧正文的新版本，不重传正文，结果参与同步。

## 退休与清理

版本退出共享历史只有两种方式，都是同步操作：

- **显式清除**：清空垃圾箱、清除指定版本或文件的历史。
- **仓库保留策略**：随索引同步，如版本和垃圾箱保留天数、每文件至少保留版本数。默认保留 365 天。由任一在线设备评估并发布退休记录，其他设备接受，不各自计算。

退休先进入**待退休**状态，宽限期（默认 30 天）内任何设备可撤销；到期后所有设备和存放点才丢弃正文并回收对象。被撤销设备在撤销之后发布的退休记录一律拒绝。

永不自动退休：当前版本、未同步的本地修改、冲突中的 head。显式清除不能删除当前版本。本机有打开句柄引用的版本，其正文在句柄关闭前本机不丢弃。设备级设置只决定本机是否保留正文，不能让版本从共享历史消失。

## 密钥、设备与撤销

- 仓库有随机生成的主密钥；内容密钥按**密钥纪元**和**子树密钥**派生，版本记录其纪元。子树密钥为将来只读分享某个目录预留，当前所有文件使用根子树。
- 每台设备有长期设备密钥对和人类可读名字。加入仓库即把当前纪元密钥封装给该设备公钥并写入成员记录；设备间认证以成员记录为准。
- **配对**：新设备显示短码或二维码，在任一已加入设备上确认即可加入；没有已加入设备在场时，凭初始化信息与恢复密钥加入。
- **撤销**：开启新纪元，新纪元密钥只封装给剩余设备，被撤销设备读不了之后写入的内容，也不再被其他设备接受连接。已有内容不重加密。任一在册设备可撤销，并发撤销按因果链先到者生效。配置了存放点的仓库，撤销设备时提示用户轮换存放点凭据。
- 设备列表显示名字、角色、最近同步时间和已同步位置，是撤销和"可安全移除"的入口。

## 密码与恢复

- 设备私钥在本机同时由密码和恢复密钥封装，密码不参与同步；改密码只更新本机保护，不重加密文件，不影响其他设备。
- 恢复密钥创建仓库时生成一次、用户自行保管，能解开任一设备的本机封装，也能凭初始化信息加入新设备；可轮换，各设备在同步到轮换记录时重新封装，旧恢复密钥随旧纪元失效。
- 忘记本机密码时凭恢复密钥重设，设备身份不变。

## 规模目标

目标为 1,000,000 个当前文件、所有未退休版本正文合计 1 TB、10 台设备、单文件 100 GB。GB/TB 为十进制：1 GB = 1,000,000,000 bytes，1 TB = 1,000,000,000,000 bytes；KiB/MiB/GiB 为二进制，1 GiB = 1,073,741,824 bytes。以下是必须实测的验收预算，**不是已执行结果**；百万文件与 100 GB 长期验收仍见待办。

| 参考配置 | 资源与存储 |
| --- | --- |
| Partial 移动级设备 | arm64、4 个 CPU 核、4 GiB RAM、256 GB 存储；保留完整索引，验收时固定目录正文不超过 8 GiB，读取缓存预算设为 8 GiB，不要求存下 1 TB 正文。受保护的未确认写入和打开句柄仍遵守正文保留规则。 |
| Full 实验设备 | 8 个 CPU 核、16 GiB RAM、SSD；每台容量须覆盖 1 TB 未退休正文、全部本机索引和暂存开销；同盘的存放点、其他 Full、Partial 缓存和可选导出副本另外计入，并预留 20% 空间。独立 100 GB 场景的三份数据至少需要 300 GB，实际须至少 360 GB 可用且满足密文/索引/暂存后的容量公式。 |

计时参考链路为源与目标共享的 1 Gbit/s（1,000,000,000 bit/s）总带宽、往返时延不超过 10 ms、无注入丢包和服务端限流；存储连续读写至少 150 MB/s（MB 为十进制）。必须记录具体 CPU、OS、磁盘、限额和实测链路；高配置宿主或仅限额的 VM 不能当作手机硬件的执行证据。

| 项目 | 验收上限与边界 |
| --- | --- |
| 设备内存 | 每台设备在冷启动、索引同步、页查询和正文同步/校验期间，进程树峰值 RSS 与跨线程实测 live heap 分别不超过 512 MiB（536,870,912 bytes）；10 台同时运行的设备进程树 RSS 之和不超过 5 GiB，存放点服务另计并报告。不得扣除索引、运行时、密码解封或正文选择元数据。 |
| 冷启动 | 本机已有百万文件完整索引、网络关闭、进程和 OS 页缓存冷态，从进程启动到解锁完成并返回首个 limit=256 目录页不超过 30 s。 |
| 页查询 | 静止规模夹具中，目录、对象键前缀和历史的每个 limit=256 页调用不超过 5 s；分别记录扫描 CPU 时间。结果有界不等于扫描成本为 O(limit)。 |
| 首次索引同步 | 存放点已发布完整夹具、目标没有索引/正文缓存，从启动已配置的新设备同步到完整索引与精确 frontier 收敛不超过 1,800 s；Partial/Full 分别验收，正文不得提前整体下载。10 台同时在线（1 台已发布的源与 9 台并发加入的目标）共享链路时，全部索引收敛不超过 3,600 s。 |
| Full 首次正文同步 | 从首次索引收敛到全部 1 TB 未退休正文可用、流式摘要校验通过且 missing_units=0，不超过 21,600 s；单目标首次同步两阶段合计不超过 23,400 s，新版本先取，中断后续传。 |
| 100 GB 经存放点同步 | 真实流式生成恰好 100,000,000,000 bytes，经独立源库、存放点、空白 Full 目标库传输；源正文导入不超过 1,800 s、发布不超过 3,600 s、目标索引不超过 300 s、目标正文不超过 3,600 s、流式读回校验不超过 1,800 s，全程不超过 14,400 s。Directory Range 与 S3 忽略 Range 的整 pack 回退分别验收。 |

夹具、计时起止、冷/暖缓存、进程/heap 测量、容量计算、断点续传和失败条件见[规模预算验收过程](scale-budget.zh.md)。流式正文窗口、单对象限制与页结果限制都不能代替本节的整进程预算；32 MiB 权限证明/快照等当前边界可能使规模验收失败，未跑长期验收前不能宣称这些目标已达到。

## 应用接入

应用通过路径与字节接入。Rust `SyncedVault` 的 `put_from_reader`、`get_to_writer`、`get_range_to_writer` 限制正文传输缓冲；`put` 借用调用者已有的字节，`get`/`get_range` 返回 Vec，结果内存为 O(对象或请求范围大小)。Core 对应 `write_from_reader`、`read_to_writer`、`read_range_to_writer` 与 `write_file`/`read_file`。不把正文缓冲有界等同于整个进程内存固定。

列表与历史提供有界结果页：Core `list_page` 列目录的直接子项（同名目录在文件前），`list_file_entries_page` 和 `SyncedVault::list_page` 按字面对象键前缀列文件；Core `file_history_page` 与 `SyncedVault::history_page` 按时间、VersionId 降序列历史。每页最多 1024 项，limit 必须在 1..=1024；续页值绑定仓库与目录/前缀，历史还绑定路径与 FileId，不持有数据库、文件句柄或服务端游标，可以序列化并跨关闭重开使用。每次调用读取当前索引，连续页不承诺跨页快照；并发插入、删除或退休会改变后续结果，路径被另一个 FileId 复用时历史续页明确失败。静止仓库拼接各页等于对应便利接口的顺序与结果。

保留 Vec 便利接口；`list`、历史、垃圾箱和冲突的 Vec 结果内存为 O(返回项数)。页接口仅限制保留的结果项数（扫描时最多 limit+1 项及一条正在解密的记录），加密索引排序可能要求每页重新扫描。完整成员/索引状态、历史遍历的已访问 ID 与待访问父版本、文件 head/路径争用者以及单个版本的正文单元引用仍有随数据增长的成本。C/Go 提供流式回调，Python 提供 reader/writer，Web HTTP 下载可流式传输；它们的 bytes/Blob 和列表便利接口仍分配完整结果，当前没有上述 Rust 列表/历史页接口。

启用同步后由后台自动同步、重连和重试。写入成功表示本机持久化完成，远端复制随后进行；EOF 前取消 reader 上传不发布部分对象，EOF 后已接纳的提交即使等待者取消也可能完成。缓冲型读取失败返回 Err，不成功返回部分对象；writer/HTTP 流可能先输出已认证前缀再失败，调用者必须丢弃输出或中止目标发布。

## 接入层

核心包含加密、文件模型、存储事务、版本与同步规则；接入层只做适配，不复制数据库、不另行定义规则，不把未实现操作报告为成功。

主要接入：Rust API、CLI、FUSE 挂载、Web。C/Python/Go 绑定跟随 Rust API，不单独加功能。WebDAV、S3 网关、TUI 降为示例，不作为验收范围。

挂载在部分设备上不让系统索引器、预览和备份工具整体扫描而把全部正文拉回本机；本机未持有的文件照常列出，读取时才获取。

## 无锁定

Full 设备在相对于本机已接纳索引 frontier 完成正文同步、且本机缓存完整时，可离线导出选中的当前文件与可选未退休历史/冲突正文，不依赖其他设备或存放点在线；这不表示已看到仍未同步到本机的新修改。追赶中的 Full 与 Partial 离线只能导出本机已有正文，`export_plaintext` 明确列出缺失目标路径，不能承诺完整导出；退休正文不保证可恢复。

导出目标必须是不存在的新目录，父路径已存在且无符号链接，所选键与归档路径须能由目标原生文件系统无损表示；`a` 与 `a/b`、大小写或原生名字/路径碰撞明确失败，不自动改名。`ExportOptions` 的历史与冲突选择相互独立，旧非头版本按原路径归档，额外 head 按 FileId 的当前导出路径归档；选择完成后版本与正文保留到导出结束，并发修改或退休不改变该次选择。正文流式输出，选择元数据随导出规模增长；损坏、认证和 I/O 错误中止，缺失报告只统计不可用正文，成功文件均为完整内容。

## 明确不承诺

- 真正并发时的默认当前版本受各设备时钟影响；它只影响展示，不丢数据。
- 仅使用 P2P、没有存放点的仓库，两台设备不同时在线就无法交换数据。
- 不提供多用户共享与权限；仓库内所有设备同权。
- 宽限期结束后退休的版本，任何设备都不再保证能恢复。
- 部分设备读取本机未持有的文件时，若没有任何持有正文的设备或存放点可达，读取明确失败，不挂起。缓冲型接口返回 Err，不成功返回部分内容；writer/HTTP 流在晚发缺失或其他错误前可能已输出认证前缀，失败后调用者必须丢弃输出或中止目标发布。
