# 技术承诺与验收标准

2026-10-29 更新。产品需求见[需求](requirements.zh.md)；本页是由它导出的技术承诺与验收标准，机制将陆续归入设计页，验收数字归入规模预算页。README 只描述当前行为，已完成与未完成的差距见[路线图](roadmap.zh.md)。API 和本机派生索引可以演进，已冻结的核心存放点格式遵守下述兼容承诺。

2026-10-17 核心收尾范围：本轮交付现有 Core、Sync、Rust API 与持久化格式，修复实际缺陷并完成原完整回归。应用、平台、新接入层与极限规模目标保留为独立待办；本页列出这些目标不表示它们已实现或已验收。

## 阶段 A 核心冻结与首批范围

核心格式版本 1 以 `crates/cipherfs-sync/tests/fixtures/core-v1/` 的当前写入器夹具冻结：加密存放点、公开测试恢复密钥、信任锚与期望明文入库，不依赖生成设备的本机数据库。以后每个版本都必须读取该夹具及随后的生产数据；已有夹具不覆盖，只追加。格式号保持 1，只作加法变更，旧字段与核心记录的含义不变。原生空库加入和独立 `recover.py export-store` 逐字节比较进入每次提交闸门。

首批范围为一台 Full、一台 Partial、一个 Directory 或 S3 存放点，1 万至 10 万个对象，经 Rust 库 API 或 CLI 接入；原系统保留副本与回退入口。原始键为 1–1024 B，16 MiB 以内对象使用单单元，更大正文自动流式分块；PATH 适配器与原始对象键隔离。认证 HTTP 对象上传复用同一流式写入器。本轮不触发真实写入；接入和连续七天比对由使用者另行启动。

生产维护配置默认关闭检查点发布/引导、GC、压实、重打包、审计发布、多段传输与恢复通知管理，只写 `v/ s/ i/ p/ r/`，新设备从原始日志重放恢复。角色字段在版本 1 中声明归属与读写/只读/管理员身份，不实施阶段 B 的角色拒绝规则；未来拒绝规则须经已认证的关键规则版本激活，不能追溯改变版本 1 的含义。

**撤销只作废尚未最终化的记录。** 阶段 A 没有最终性锚、GC 或原文压实，授权闭包按有效截止重算，拒绝记录仍可保留为原始证据。后续阶段 B 只能增加最终性与维护机制，保持核心可恢复；已最终化记录不因迟到撤销而回滚，不能把尚未实现的最终性承诺为本轮交付。

## 术语

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

## 加密

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

## 威胁模型

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

## 共享索引

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

## 正文同步与设备保留

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

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

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

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

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

### 可安全移除

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

### 首次同步

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

## 存放点

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

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

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

对所有存放点的要求：

- 远端对象打包，减少对象数量；远端 pack 目标比本机小（4–8MiB 量级），使被整体下载的 pack 和范围读取都划算。
- 对象名含长度或哈希；读到不一致视为未到达并重试，不报损坏。
- 退休生效后，存放点对象再延迟一段短时间回收，应对最终一致；内容寻址使误删只需重传。
- 范围读取可用时按块获取；不可用时按对象整体获取。
- **凭据按设备分发，写入设备默认无删除权**：每台设备持有自己的存放点凭据，只有写入、读取、列举权限；删除只属于执行 GC 的角色或由用户显式授予。支持时开启桶的对象锁或版本保留，保留期不短于 GC 延迟，被攻破的设备和勒索软件删不掉密文。撤销设备时提示轮换该设备的凭据。凭据来自可插拔来源（环境变量、文件、systemd credential、系统钥匙串、远端密钥服务），不写入仓库和日志。
- 名词：桶、目录、云盘是**存放点**，只见密文；守护进程对应用说 S3 协议的那一侧是**前门**，见明文，两者不混用。

### 多卷存放点

一个 Directory 存放点可以由多个卷组成，对仓库、其他设备和 `recover.py` 表现为一个 StoreId。每个卷是一个独立挂载的目录，持有同一份 `cipherfs-store.json`、`s/` 标记和一个卷描述文件；卷上的对象布局与单卷 Directory 存放点完全相同，核心格式不变，核心读取器忽略卷描述文件。

- **放置以文件为单位。** 每个 FileId 固定分配 `copies` 个卷（默认 2，取值 1 到卷数），该文件全部版本的正文 pack 只写入这些卷，每个卷都持有该文件的完整正文；一个文件的正文不跨卷拆分。正文 pack 只合并同一卷集合的文件的单元。
- 日志段 `i/`、标记 `s/`、恢复对象 `r/` 及其多段载体写入每一个卷。任何一个卷单独交给参考导出器，结果是：上面的文件要么完整、要么不存在，没有半截文件；缺失的文件逐一列出。
- 写入等待级别"存放点已有"在该文件的 `copies` 个卷全部持久化后才成立；只到达部分卷的对象视为未到达，下一轮同步补齐。
- 新文件按卷剩余空间选择卷；单个文件不能大于任一候选卷的剩余空间，不满足时写入明确失败，不拆分、不降低份数。
- 文件到卷的映射是派生状态，列举各卷即可重建；本机只缓存，缓存丢失不影响正确性。
- 维护命令只复制与校验，不删除逻辑存放点上的对象：**加卷**后新文件自然流向它，不重平衡；**撤卷**把该卷上每个文件补齐到其他卷的 `copies` 份、按对象名校验后把卷标记为脱离，物理拔盘由人完成；**修复**让每个文件重新满足 `copies`，副本来自其他卷或持有正文的设备。生产剖面的零 DELETE 不变。
- 卷缺席时存放点降级：读取走其他持有该文件的卷，仍有不少于 `copies` 个在线卷时写入继续，否则写入明确失败。
- 阶段 B 的压实和重打包只在同一卷集合内合并 pack。
- 验收：六卷 `copies = 2` 的真实目录夹具下，每个文件恰好在两个卷上完整、`i/ s/ r/` 在六个卷上一致；撤卷与修复后两设备 `fsck` 与独立导出逐字节一致；单卷独立导出只含完整文件并列出缺失；超过单卷余量的写入明确失败；全程客户端 RSS ≤ 512 MiB，存放点只出现五类对象且零 DELETE；永久夹具字节不变。

## 文件元数据

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

**内容类型与属性。** 每个文件版本带必选的内容类型（小写 `type/subtype`），每个文件版本和显式目录条目带开放的键值属性列表。写入方每次写入内容时从字节判定类型，不看文件名，并记录图片尺寸，JPEG 另把内嵌 EXIF 缩略图记为一段范围（`cipherfs.preview`），在预览结束处切内容单元，使 Partial 设备可单独读取。`cipherfs` 命名空间保留给这类派生值，其他命名空间自由使用，值是不透明字节，各设备原样保留。设置属性发布内容与路径不变的新版本，与移动相同，并发修改成为冲突头。属性到文件系统 xattr 的映射不在此范围。

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

每个文件的版本构成因果链，删除是一个 tombstone 版本，移动和重命名是改变路径属性的版本，不产生历史或垃圾箱条目；并发的修改跟着文件身份走，不因对方移动而在旧路径复活。目录随文件存在，删除目录即删除其下每个文件；`mkdir` 发布显式目录条目，因此空目录会同步，两台设备创建同一路径时合并；`rmdir` 对该路径所有条目发 tombstone。

**本机专属条目。** `.DS_Store`、`._*`、`Thumbs.db`、`desktop.ini` 等垃圾和 `target/` 这类编译目录既不能拒绝创建（编译要能跑），也不能硬编码名单，所以做成数据里的规则：共享策略携带 gitignore 语法的 `local_only_patterns`（`init` 写入出厂值）与 `local_only_rule_files`（默认 `.cipherfs-local`），每台设备可加自己的模式（不同步），同步树里的规则文件按目录生效、近者优先、支持 `!` 取反；优先级从低到高为策略、设备、规则文件。命中的新文件或目录，以及在本机专属目录下创建的一切，都是本机专属：只存在本机表里，单一版本，正文在本机内容库，各处列表标 `local_only: true`，不属于签名日志、快照、同步、GC、保留、历史、垃圾箱或冲突视图。只在创建时判定，重命名不改变状态；同路径的已发布条目到达时替换本机文件，目录合并。创建与写入 API 的 `publish: force` 参数可强制发布命中的新条目。导出默认不含它们。

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

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

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

### 当前版本

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

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

### 冲突的呈现与解决

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

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

### 恢复

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

## 退休与清理

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

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

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

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

## 密钥、设备与撤销

- 仓库有随机生成的主密钥；内容密钥按**密钥纪元**和**子树密钥**派生，版本记录其纪元。子树密钥为将来只读分享某个目录预留，当前所有文件使用根子树。
- 每台设备有长期设备密钥对和人类可读名字。加入仓库即把当前纪元密钥封装给该设备公钥并写入成员记录；设备间认证以成员记录为准。
- **配对**：新设备显示短码或二维码，在任一已加入设备上确认即可加入；没有已加入设备在场时，凭初始化信息与恢复密钥加入。
- **撤销**：开启新纪元，新纪元密钥只封装给剩余设备，被撤销设备读不了之后写入的内容，也不再被其他设备接受连接。已有内容不重加密。任一在册设备可撤销，并发撤销按因果链先到者生效。配置了存放点的仓库，撤销设备时提示用户轮换该设备的存放点凭据。
- **定期纪元轮换**：仓库策略可设定轮换周期（默认关闭，建议 90 天），到期由任一在线设备开新纪元并封装给全部未撤销设备；泄漏的纪元密钥只能解到该纪元为止的内容。已披露内容不能撤回。轮换周期与下面的公示期同在一条共享的仓库策略记录里，任一有管理权的设备可改，因果最新者生效。
- **恢复设备的管理权（公示期）**：凭恢复密钥加入的设备立即能读写，但撤销、批准配对、轮换恢复密钥、认可其他恢复加入要么由一台独立既有成员认可，要么走公示期：它公开发布管理权申请，等满仓库策略规定的公示期（默认 7 天，最小 1 天）后生效；期间任一既有成员（加入先于申请、未撤销）反对则申请作废，反对者可随即撤销该设备并轮换恢复密钥。既有成员在接纳“申请生效”记录之前始终有权反对，程序遇到生效记录而本机尚未表态时必须先询问用户；无人值守的服务默认在首次看到申请后等满公示期才自动接受。反对永远是显式动作。公示期保护的是控制权：恢复密钥被盗意味着内容已可读，这一点公示期不改变。
公示期已在 Core、同步后台、CLI、HTTP 与 Rust/C/Python/Go 绑定落地。`recovery claim|claims|accept|object|activate` 提供公示与显式表态；`recovery auto-accept false` 改为无人值守只告警，`policy set recovery-notice-days N` 修改以后公示的时长。时长固定在申请的因果位置；以后改策略不会缩短进行中的公示。`/health` 显示待本机表态数量；Dioxus 桌面 App已提供公示横幅和显式接受/反对，桌面通知仍随原生壳交付。

- 设备列表显示名字、角色、最近同步时间和已同步位置，是撤销和"可安全移除"的入口。

## 本机解锁、密封与恢复

- 本机存储密钥与设备种子由一组**解锁来源**各自封装，封装可并存、可单独增删，只改本机不碰共享状态：密码；恢复密钥；服务密钥（文件、环境变量、systemd credential）；远端密钥服务（KMS、Secrets Manager、自建 HTTPS，配云平台身份或可撤销 token）；系统钥匙串与安全硬件（Secure Enclave、TPM、Android Keystore，对应生物识别解锁）。密码不参与同步；改密码只更新本机封装，不重加密文件，不影响其他设备。
- **密封**是仓库的一等状态：进程在运行但密钥不在内存。启动即密封、闲置超时密封、手机退后台密封、运维或用户经统一接口解封；服务器配置远端或服务密钥来源时重启后自动解封。
- 设备私钥可选绑定安全硬件，签名不出硬件，磁盘镜像被拷走也不能冒充设备；此时恢复加入产生新设备身份而不是保留旧身份，文档说明该取舍。
- 进程内密钥材料清零、锁定内存不换页、关闭 core dump、不进日志；对密钥的这些要求由测试或审查覆盖。
- 每种解锁来源防什么、不防什么写成表：磁盘镜像或备份泄漏（密码、远端解封、硬件绑定可防；服务密钥文件不防）；运行中被取得 root 或读内存（都不防）；存放点提供商（都防）；虚拟机提供商读内存（都不防）。
- 恢复密钥创建仓库时生成一次、用户自行保管，能解开任一设备的本机封装，也能凭初始化信息加入新设备；可轮换，各设备在同步到轮换记录时重新封装，旧恢复密钥随旧纪元失效。
- 忘记本机密码时凭恢复密钥重设，设备身份不变。

## 格式与算法升级

共享对象的字段与算法演进不重写已有认证记录的原始字节。共享 wire format 1 在恢复公示记录落地后冻结；本机 metadata schema 1、CAS schema 1，不读取更早的开发格式。

- **编码**：索引段、快照、配对消息、本机数据库值和仓库配置使用 protobuf。当前未发布格式与 schema 均编号1；对象规模替换可删除旧类型，不读取旧开发格式、不写迁移。已移除的记录15和对象名6保留编号而不复用。普通未知字段可以跳过投影，但未知、缺失或混合的记录类型拒绝整个段，不能过滤后推进 frontier；保留未知认证 body 字节不代表能应用未知语义。
- **身份即字节**：被哈希或签名的记录以写入方编码一次的原始字节携带，读取方先认证再解码，不重编码计算身份；重开、快照与转发逐字保存。版本、退休、成员入会和权限证明遵守此规则，派生成员名字、角色和能力视图不修改入会原像。配对仍是对原始 transcript 的 keyed BLAKE3 MAC/SAS，不增加独立签名。
- **算法标识**：哈希、签名、公钥、KDF、AEAD、分块和对象名编码使用独立注册号。公开摘要/公钥/签名文本为小写 Crockford base32 的 `算法-<base32>`，如 `b3-`、`ed25519-`、`x25519-`；16-byte FileId、SubtreeId、StoreId、VaultId 使用无前缀 base32（FileId 由密钥派生，其余为随机身份），不伪装成哈希。所有 ID 与对象名不依赖大小写；每个存放点在 `cipherfs-store.json` 中声明对象名编码。公开 JSON 身份使用规范字符串，拒绝旧 hex、错误类别、空白和非法编码。注册表记录 active/deprecated 与截止时间；未知、未实现用途或过期算法必须拒绝，不可降级。
- **仓库钉算法**：Genesis 记录钉死本仓库的算法集合，不符合的记录拒绝。运行中切换算法需要新的签名策略记录，机制在 ADR 中定义，先不实现。
- **格式号与能力通告**：`CFSSEG02` 头含被签名/AAD 认证的 format 和算法号，当前实际支持上限为 1。入会绑定 `max_format >= 1`，设备本人可更新当前能力；写出取本机实际上限与全部未撤销成员通告的最小值。完整外签名验证后，更高 format 明确报错，在候选钥处理与应用前停止；已有授权作者的普通导入持久化阻塞该位置及后续段，其他无依赖设备继续。bootstrap 不能越过阻塞，也不能从未认证头建立阻塞。
- **本机数据库**：metadata 和 CAS 的 protobuf settings 存 schema 号，只有明确新建文件才初始化 schema（metadata 为 1、CAS 为 1）。已有库缺失、损坏或零 schema 报损坏，其他 schema 号报不支持，不静默重建，也不迁移。原有加密与未加密边界保持，配置显式标注 KDF。
- **对象名**：按[当前架构](architecture.zh.md#持久化与公开身份)使用规范算法文本、哈希和长度；恢复对象名绑定纪元、完整 wire 哈希和长度；旧索引快照已删除。未知算法对象记诊断并忽略，不能授权或作为 GC 删除依据。

## 规模目标

目标为 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 权限证明/快照等当前边界可能使规模验收失败，未跑长期验收前不能宣称这些目标已达到。

### 对象规模目标（未实现、未验收）

增加独立的对象规模剖面：39,000,000 个写一次对象，正文合计约 1.8 TB，每天 13,500 次写入、每秒 16 次随机读取；一台 Partial、一台 Full、一个 S3 存放点。对象键是 1–1024 字节的任意字节串，与文件路径隔离；对象身份由仓库共享的稳定秘密派生，同键并发写仍保留冲突头。对象实际长度在 16 MiB 以内时使用单单元、不嗅探类型、不拆预览，未知长度输入也遵守此规则；更大正文自动流式分块。该剖面不替代上面的通用文件、长历史、100 GB 单文件和移动设备目标。

| 项目 | 必须实测的目标与证据 |
| --- | --- |
| 增量成本 | 正常点读写、空闲同步轮、一次增量维护不做无条件全库扫描；以 10⁴、10⁵、10⁶ 三档比较扫描/解密行数、事务及 LIST/GET/PUT 次数，应与总对象数无关。首次索引、完整遍历、检查点构建、策略重算和 fsck 可以随相关数据增长，但须流式、有界内存、可恢复。 |
| 百万对象基线 | 空闲同步轮小于 100 ms，打开仓库小于 1 s，每对象本机索引不超过 900 B（含 redb 实际分配、辅助表和空闲页）；单对象写入为 1 次 pack fsync 和 1 次 redb 提交。冷暖状态、审计模式、数据库缓存、全部持久化开销与测量边界须分别声明。 |
| 两设备全键验收 | 保留全部真实键和长度，在独立 Partial、Full、Directory 存放点上使用按真实长度生成的合成正文；正文量可按宿主实际可用磁盘截断，键数不得截断。记录 Full 建索引时间、空 Partial 从检查点起步时间、空闲轮计数、每对象本机字节及命中/未命中 P99。无法取得真实键时使用 10⁷ 合成键，并明确报告“真实键验收未执行”。Directory 的结果不能冒充真实 S3 网络验收。 |
| 内存 | 客户端进程树 RSS 和 live heap 分别仍不超过 512 MiB；服务器另列 `database_cache_bytes` 等配置及自己的 RSS/heap 上限，不以服务器结果替代客户端预算。 |
| 查询与配置 | 对象及路径查询使用各自命名空间；哈希序分页返回不透明游标。S3 前门每桶固定 `list_order=hash|lexical`，字典序目录超过配置上限时报错，不静默切换顺序。阈值有单位、文档和组合校验；自适应有确定性规则、min/max、变化事件及可钉死选项，不调整共享安全策略。 |

**最终性目标收窄撤销承诺：撤销只作废尚未最终化的记录。** 已由最终性锚确认的版本、成员引入、退休、槽位封闭和 GC 授权不因迟到撤销、撤销撤回或权限级联失效而回滚；不承诺事后抹去已共同最终化的受损设备记录。未最终化后缀仍按有效撤销截止和权限闭包重算。锚绑定前锚，签字资格取前锚处全部未撤销、未休眠成员；候选不能自行缩小签字集合，每设备对同一前锚只签一个候选，签字锁先持久化。缺少必要签字时不可逆回收停顿，不能用本机墙钟或离线时长绕过。

物理删除只执行已经锚定的精确计划，宽限时间只是额外下限；待删除 PackId 不能重新成为持久化依赖，幸存包的迁移也必须先完成锚定移交。压实和晚分支重基依共享锚决定，不能依赖某台设备是否还保留旧存根。这些最终性和对象能力当前尚未实现；上述预算、协议审查和短回归均不构成规模验收通过。

## 服务器场景

应用把仓库当作自己的存储层：本机只是热缓存，全量在存放点和其他设备上，缺的正文按需获取。与个人多设备共用同一核心、同一归属进程模型和同一套同步，只是壳不同。

- **守护进程** `cipherfs serve`：独立进程持有仓库，经 Unix socket 或 TCP 提供服务；配置文件集中声明监听地址、每个仓库的路径与解锁来源、存放点及其凭据来源；提供就绪与健康接口，可服务多个仓库。应用进程不碰密钥，多进程应用共享同一个守护进程。嵌入进程内只用于桌面 App 和单进程工具。
- **S3 前门**：守护进程对应用说 S3 协议，应用用现成 SDK，改 endpoint 即迁入。桶对应仓库或前缀，键对应路径，Range GET 为范围读，分片上传为流式写，版本接口暴露历史和并发 head，ETag 策略可配置，支持预签名 URL。默认只听回环地址；监听其他地址必须显式打开并强制 TLS；每请求 SigV4 签名，校验 Host 头，不支持 Basic 认证；前门密钥按应用发放、可轮换，与存放点凭据互不相关。前门处是明文，守护进程所在机器是信任边界。仓库独有能力（写入耐久确认、固定、设备与存放点管理）经 JSON 接口提供。
- **写入耐久确认**：写入可指定等待级别——本机持久化、至少一个存放点确认、至少 N 台 Full 设备确认——满足后才返回。
- **保留策略可应用化**：允许很短的退休与宽限时间，删除的附件可以很快回收。
- **固定与预取**：按单文件固定，按最近访问热度预取，降低冷读延迟。
- **按 FileId 读写**的直接入口，应用不必维护路径映射。
- 服务器是一台有自己身份的设备，角色和其他设备一样可选；守护进程未指定时默认 Partial 加预算。被攻破的服务器按设备撤销处理。

## 应用接入

应用通过路径与字节接入。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，HTTP 下载可流式传输；它们的 bytes/Blob 和列表便利接口仍分配完整结果，当前没有上述 Rust 列表/历史页接口。

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

## 访问审计

2026-10-18 设计定稿，2026-10-24 完成实现与现有入口功能验收（见[访问审计](access-audit.zh.md)）；当前的签名索引、版本历史、运行日志和 HTTP tracing 都不构成访问审计。审计是**文件访问日志**：哪台设备（服务器上再加哪个应用）在什么时候对哪个 FileId 的哪个 VersionId 做了什么操作、结果如何、多少字节。它是服务器线能力，个人多设备线共用同一能力但默认关闭。完整设计（字段、事务边界、容器、验收）在开发仓库的设计文档中，摘要见 [ADR 006](adr/006-access-audit.zh.md)。

- 审计是 Core 的统一能力，所有接入层复用；覆盖经 Core、Rust API、CLI、绑定、FUSE、桌面 App、HTTP API 与 S3 前门发生的访问，包括缓存命中、历史/冲突版本读取、失败尝试和密封状态下的拒绝；后台同步、预取、退休与 GC 以系统身份另行记录。
- **和正文一样保存**：每台设备写自己的流，密钥从仓库纪元密钥与设备 ID 派生，用设备私钥签名并成链，封成不可变对象发布到所配置的存放点的独立前缀。它不进共享索引，不影响 FileId、VersionId、成员权限、文件保留或 frontier；索引同步与 GC 不引用也不删除它。
- **仓库里所有未撤销设备都能读**，按文件（谁动过这个文件）或按设备（这台设备做过什么）查看；被撤销的设备读不到新纪元的日志；恢复密钥能读全部历史。不另设审计接收方或收集服务器。
- 只记 ID：目标是 FileId 与 VersionId，目录操作记目录 FileId，查找不存在的名字只记父目录与一次未命中。事件只含有类型的白名单字段，不记录密码、密钥、凭据、令牌、请求正文、文件名、路径或任意错误文本。
- 三种模式按入口配置：`off` 明确关闭；`best_effort` 不采样、可降级但必须显示丢失计数与缺口；`required` 在无法持久化审计意图或提交事实时拒绝新的业务访问，不静默退化。守护进程的 HTTP/JSON 与 S3 前门默认 `required`，守护进程提供的挂载默认 `best_effort`，嵌入式库与 CLI 默认 `off`。文件变更与其提交记录写在同一个本机事务里。
- 日志保留与文件历史保留独立；清理只由持有删除权凭据的设备执行，写入设备不能删除已发布的日志。
- 对外状态必须说明模式、已覆盖入口、未完成操作、积压、丢失数与最后持久/发布序号。签名与链能让任何成员验证一台设备的流未被改写或抽段；尚未发布的本机尾部不承诺防毁，控制了运行中进程的攻击者可伪造后续记录，设计不声称更强。
- 验收以可检查的入口覆盖表为准：部分入口、`best_effort` 或仅通过普通日志测试都不计为完整审计交付。

## 接入层

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

主要接入：Rust API、CLI、FUSE 挂载、桌面 App、HTTP API 与守护进程 S3 前门。C/Python/Go 绑定跟随 Rust API，不单独加功能。早期 WebDAV/S3 网关和 TUI 原型不作为验收范围；守护进程前门按上面的服务器场景需求验收。

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

## 无锁定

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

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

### 自描述仓库

没有本软件也要能拿回数据。仓库自带读取它所需的全部说明，默认开启，创建仓库时可关闭；关闭只影响是否写入说明文件，不改变格式。

- **规范文档**：仓库根目录保存明文 `FORMAT.md`，精确到字节：目录布局与对象命名、ID 文本字母表与存放点描述文件；`CFSSEG02` 等容器头的字节布局以及哪些字节进入签名、哪些进入 AEAD 的 AAD；密钥链的每一步（密码 → Argon2id 参数 → 本机封装 → 主密钥 → 纪元密钥 → 子树密钥 → 内容密钥）及每个 BLAKE3 域标签原文；算法注册表中每个 ID 对应的公开标准引用；独立分发的 `cipherfs.proto` 原文；只持有密码或恢复密钥时逐步解出当前文件列表与正文的操作顺序。文档以读取者视角撰写，不从实现倒推。
- **测试向量**：仓库根目录保存公开黄金向量及其期望输出，覆盖规范中的每一步，供实现者逐步核对。
- **参考解密器**：一个单文件脚本，用非 Rust 的通用语言实现，只依赖公开标准算法的实现，功能限于凭密码或恢复密钥列出文件并导出明文，不做同步与写入。它随仓库和发布包分发，是规范的可执行形式。
- **一致性由测试强制**：仓库内的 `FORMAT.md` 与代码仓库中的规范文件逐字节相同；规范中的魔数、HKDF 标签、算法参数与代码常量逐一比对，不手抄；完整验证必须用参考解密器、不借助 Rust 代码，解开向量仓库并与明文一致。
- 存放点可选保存同一份规范与解密器（默认开启，添加存放点时可关闭）。这些明文只说明格式，不含密钥或用户数据；对象名已经表明存放点属于本软件，不构成新增泄漏。

## 明确不承诺

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