Files
NotesAgentic/docs/contracts/Sync-v1契约.md
T

130 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sync Protocol v1
状态:服务端协议已实现,PostgreSQL 17、MinIO 与双 Worker 的 S-04 生产并发验收已通过;其余服务运维门和发布端到端验收仍在推进。入口为 `server sync/sync_server`;运行时 `/openapi.json` 是字段约束来源。SQLite Fixture 只用于协议回归,不能代替生产依赖验收。
## Rust 上传客户端增量
Workspace schema 3 新增持久绑定、上传作业与远端 heads。首次向已验证为空的远端绑定后,队列保留每次本地操作;大正文转入按摘要命名的 spool,清除已物化 outbox 的正文副本。提交基线来自远端确认值,首次发送时冻结,超时重试不得重算。确认响应逐字段核对后,heads、作业与 outbox 同事务更新。解绑封存旧绑定与队列,重新绑定从当前文件快照生成新操作,旧回调不能修改新绑定。
Rust HTTP 客户端已实现握手、登录、空远端复核、1 MiB 分块上传、查询 offset 续传、complete 和 Revision 提交。默认 HTTPS,测试 HTTP 必须显式启用;不跟随重定向,令牌不进入 URL,响应有大小限制。真实本地 HTTP Fixture 已验证 20 次编辑形成 20 个正确远端基线、同一提交重复 100 次无重复 revision。此 Fixture 使用 SQLite/磁盘对象,不替代生产 PostgreSQL/MinIO 验收。
桌面 Sync capability 已接入 Stronghold 会话、冲突解决 UI、附件续传与逻辑数据分类;本地真实 HTTP Fixture 只证明客户端协议行为,不替代 PostgreSQL/MinIO、备份恢复和发布包验收。
Workspace schema 4 增加持久 inbox、分页 boundary 和冲突记录,并为文件移动/删除日志记录 local/remote 来源。下载先验证长度与摘要,再登记 inbox;文件操作通过 Workspace journal 应用后才推进 cursor。进程在文件提交与 cursor 提交之间重启时使用操作回执去重。远端新增保留 file_id,远端移动/删除不回流 outbox。本机历史提交回放仅确认操作,不回退新编辑。本地待上传修改或摘要不符产生冲突,保存本地与远端内容引用后推进接收游标;同一文件保留最新待处理冲突,旧冲突记录仍保留。空本地库可以绑定已有远端并仅拉取。
真实本地 HTTP 集成已验证第二设备拉取 20 个历史 revision、稳定 file_id、零回流 outbox、本机旧历史不覆盖新编辑,以及离线同改时保留本地内容和远端冲突。重启边界测试验证文件提交前后 cursor 都不会提前推进。冲突解决事务、完整附件故障矩阵和真实双机 UI 验收尚未完成。
## 身份与数据边界
生产 CLI 只接受 `postgresql+psycopg`,账号通过 `python -m sync_server create-user` 交互初始化。无开放注册、默认密码或内置共享账号。设备由每次登录注册;Access Token 有效 900 秒,Refresh Token 30 天,数据库只保存摘要。刷新轮换使旧会话立即失效。注销删除当前会话;设备撤销使该设备所有会话与上传立即不可访问。社区与 Sync 身份完全独立。
普通读写须带 `Authorization: Bearer …`。访问对象也必须验证 Vault 所有者和设备;摘要不是访问凭证。服务器可见明文,**不支持 E2EE**、多人共享和 CRDT。
## 接口
所有业务接口位于 `/sync/v1`。错误为 `{error:{code,details}}`;校验错误不回显输入。无正文与凭据访问日志。
| 方法与路径 | 行为 |
| --- | --- |
| GET `/handshake?protocol=1` | 限额、协议与保留策略;不兼容为 426 |
| POST `/auth/sessions` | username/password/device_name,登录失败限流 |
| POST `/auth/refresh` | refresh_token,轮换会话 |
| DELETE `/auth/sessions` | 注销 |
| GET `/devices`DELETE `/devices/{id}` | 列出及撤销当前用户设备 |
| POST、GET `/vaults` | 创建、列出当前用户 Vault |
| POST `/vaults/{id}/uploads` | content_hash/size,预留配额,一小时上传期 |
| GET、PUT、DELETE `/vaults/{id}/uploads/{upload}` | 查询 offset、按 offset 写入、取消 |
| POST `/vaults/{id}/uploads/{upload}/complete` | 长度和 SHA-256 复核,写入对象存储 |
| POST `/vaults/{id}/revisions` | 幂等提交与 CAS |
| GET `/vaults/{id}/changes` | cursor/limit/boundary,一致分页边界 |
| GET `/vaults/{id}/history/{file_id}` | before/limit 倒序历史 |
| GET `/vaults/{id}/objects/{hash}` | 经鉴权读取与摘要复核 |
上传分块最大 1 MiB,对象最大 100 MiB。offset 冲突为 409 `UPLOAD_OFFSET`,响应给出已确认偏移;客户端查询偏移后续传。暂存文件长于数据库 offset 时先截断并 `fsync`,短于 offset 或无法读取时删除损坏上传并释放预留,再返回 409 `UPLOAD_DAMAGED``restart_required=true`。完成请求响应丢失时用同一 upload ID 重试,持久回执返回原摘要且不会重复计费;重新预申请同一摘要也会确认对象已存在。未完成上传不允许提交 Revision。
提交字段:operation_id、file_id、base_revision、path、operation (`put`/`delete`)、content_hash、size。device_id 从会话取得,客户端不能冒充。移动使用同一 file_id、新 path 及当前对象;恢复历史使用历史摘要、新 operation_id 和当前 base_revision,生成新 Revision。删除必须携带当前基线、空摘要和 size=0。
Vault 行锁内执行幂等键验证、CAS、路径检查、序列分配及历史和当前元数据写入。相同请求重试返回原结果;改变请求内容或设备重用同一幂等键为 409 `IDEMPOTENCY_REUSED`。基线不匹配返回 `REVISION_CONFLICT` 及当前 Revision,客户端必须保留本地内容并显示冲突。
路径须为 NFC,拒绝 Windows 保留名、控制符、穿越、反斜杠、绝对路径、尾随点/空格与 `.ainote`/`.git`。大小写折叠后检查同名及文件/目录前缀冲突。文件身份不由路径推导。
## 保留与未交付边界
当前历史和 tombstone **永久保留**,游标不主动过期;不启用对象 GC,以免缺少恢复演练时删除历史对象。配额包含全部历史对象与未完成上传的预留额度。每个 Worker 每 60 秒清理已过期上传;清理与 PUT/complete 使用相同 Vault 行锁顺序,先删除暂存文件再删除预留记录,失败可在下一轮重试。通知尚未实现,客户端必须主动按 cursor 拉取。对象 GC、限速指标、完整备份恢复工具与生产负载验收仍未交付。`/ready` 会在受控缓存周期内检查数据库 schema、暂存目录写入/`fsync`/读回及 S3 临时对象写入/读回/删除,任一依赖异常返回 503。
验证向量见 `server sync/tests/test_protocol.py`:双设备 CAS、幂等重放、移动与删除、历史恢复、稳定分页、隔离、撤销、刷新、配额、路径冲突、摘要与断点。
## 逻辑任务记录 v1
普通文件传输增加保留命名空间 `opennexus-records/v1/tasks/task_<32位小写十六进制>.json`。文件 Revision 的 file_id 与任务业务 id 分开;记录仅包含 `schema=1``kind=task``id``data`。data 白名单为 title、description、status、note_id、due_at_ms、created_at_ms、updated_at_ms。时间采用 UTC Unix 毫秒,due_at_ms/note_id 可为空;status 为 todo/in_progress/done/cancelled。任务状态只表示任务状态,不启动目标设备上的 Agent 或后台作业。
Host 在写入 journal、捕获外部修改和上传前验证记录;未知字段、未知 schema/kind、非法路径或时间拒绝。记录不允许 api_key、token、environment、permissions 等附加字段。标题最多 4096 字节、说明最多 256 KiB、记录最多 1 MiBCore list 响应最多 4 MiB。用户写入标题/说明的正文仍是用户内容,不按关键字审查正文。
桌面 Task CRUD 经 Host records broker,操作重放返回原始记录且同 ID 不同字段拒绝;Core 不以全局 SQLite 作为任务来源。已明确归属于当前 Vault 的旧 Task 表在首次访问时逐条迁移,完成后设置所有权标记,来源表保留;未分配 Vault 的全局旧数据不猜测归属。文件身份采纳时,任务链接通过 Host 别名解析,并在上传队列物化前将规范化引用写成新的逻辑记录。
用户 Skill 与六类可选数据的独立白名单见下文;所有类别均使用逐字段逻辑记录,不得用复制任意 JSON/SQLite 代替。
## 主题与编辑器偏好记录 v1
主题设置使用 `opennexus-records/v1/theme-settings/appearance.json`kind 为 theme_settings、id 为 appearance;编辑器偏好使用 `opennexus-records/v1/preferences/editor.json`kind 为 preferences、id 为 editor。两者沿用 schema/id/kind/data 信封、文件 CAS、幂等操作和冲突保留规则。
主题白名单为已安装主题标识、编辑字体与字号、行高、代码配色、六级标题字体大小与字重;偏好白名单为恢复上次库的布尔偏好、自动保存间隔、语言、编辑模式、行宽、拼写检查、Markdown 格式与最多 20 项格式预设。Rust 拒绝未知嵌套字段并限制字符串、枚举、数量和数值范围。不传输实际 Vault 路径、凭据、权限、环境变量或自定义主题程序包。目标设备缺少主题时使用内置浅色主题并显示提示。
前端按 Vault 和记录类别持久保存草稿、基线摘要与操作 ID;重启恢复原草稿,CAS 冲突必须选择保留本地或采用 Workspace 版本。选择期间出现新编辑时保留新编辑。远端应用不产生回流。轮询空库不自动创建设置;初始上传和合并预览显式写入当前偏好,空库下载不预写设置。
## 侧栏布局记录 v1
`opennexus-records/v1/layout/sidebars.json` 使用 schema=1、kind=layout、id=sidebars。data 仅包含 primaryExpanded 布尔值、workspaceWidth 与 chatWidth 两个 200–520 的像素数值;允许小数以保留指针拖动结果。未知字段、窗口路径或越界宽度均拒绝。记录使用相同的 Vault 草稿、CAS 和冲突解决机制,初始上传/合并准备包含当前布局。
本机已有三项侧栏 localStorage 值仍作为初始偏好读取并保存,不删除来源。组件共享布局状态,远端值立即反映在侧栏;窗口可用空间不足时仅收窄渲染宽度,不改写同步偏好,恢复空间后恢复偏好宽度。布局记录不含本机窗口坐标、显示器信息、已打开文件路径或执行权限。本版本未支持的旧客户端不能被视作已验证兼容,跨版本发布兼容验收仍需覆盖新增记录类别。
## 用户 Skill 记录 v1
用户创建的声明式 Skill 使用 `opennexus-records/v1/user-skills/user_skill_<32位小写十六进制>.json`kind 为 `user_skill`,业务 ID 与文件 `file_id` 分离;`user_skill_` 前缀由此命名空间保留,安装包不得占用。data 白名单为 version、name、description、prompt、tools、permissions、retrieval、required_capabilities、created_at_ms、updated_at_ms;记录信封和内容摘要继续作为文件 CAS 与冲突依据。用户 Skill 默认参与当前 Vault 同步。
name 为 1128 个 Unicode 字符且不能全为空白,description 最多 2000 字符,prompt 最多 64000 字符。tools 最多 64 个、permissions 最多 32 个、required_capabilities 最多 16 个;各列表不得重复,标识仅允许 ASCII 字母、数字、点、下划线和连字符。retrieval 只含 top_k 1100、rerank、citation;模型能力和权限采用当前公开枚举。时间为非负 UTC Unix 毫秒且 updated_at_ms 不早于 created_at_msversion 不超过 JavaScript 安全整数。整体记录仍受 1 MiB 上限约束。
同步的 permissions 是 Skill 对工具需求的声明,不是目标设备授权。API Key、令牌、设备授权、启用状态、正在运行的 Agent、安装目录、包来源、环境变量和秘密值均不进入记录;新设备按本机 Tool 可用性、权限策略和模型能力重新判断。缺少工具或声明不足的记录可编辑和同步,但不可选择运行;用户明确选择一个 ready 的 Skill 后,每次工具调用仍经过设备本地 PermissionManager。
Core 经 Host 提供按 Vault 的 list/get/create/update/delete,创建 ID 从操作 UUID 稳定派生。相同操作 UUID 和相同载荷返回 Host 持久化的原回执;相同 UUID 配合不同 ID、CAS 基线、操作类型或字段值返回 `USER_SKILL_OPERATION_CONFLICT`。更新和删除必须携带 64 位内容 revision;过期 revision 返回 `USER_SKILL_REVISION_CONFLICT`。前端切换 Vault 后丢弃迟到响应,不能把旧 Vault 的列表或保存结果发布到新 Vault。
## 工作区人设记录 v1
桌面端使用 `opennexus-records/v1/persona/default.json`schema=1、kind=persona、id=default。data 白名单为 version02^531 整数)、name(最多 128 Unicode 标量)、system_prompt(最多 16000 Unicode 标量)、dialogue_pairs(最多 20 对,每对仅 user/assistant,各最多 8000 Unicode 标量);整条记录仍受 1 MiB 字节限制。人设正文是用户内容,不自动赋予权限或启动任务。未知字段一律拒绝。
Core 通过绑定 Vault 的 workspace.persona.get/write RPC 读写;Host 先校验 Vault、路径和数据,再执行现有 CAS journal。设置 HTTP DTO 增加 revision 内容摘要,供表单保存时作为 expected;此摘要不写入逻辑 data。整数 version 用作显示版本,不能代替摘要 CAS。重复 operation_id 与同一输入返回持久回执,回执包含所提交记录的实际摘要。过期摘要以 PERSONA_VERSION_CONFLICT 返回,表单保留错误状态。
桌面运行的聊天/Agent 获取当前 Vault 人设,缺失记录得到空人设,不回退全局 SQLite。Web 模式保持原来的全局存储。旧全局数据保留,不自动复制到任意 Vault;用户可通过下述预览流程明确选择导入当前 Vault。表单加载时保留 Vault 身份,切换 Vault 后禁止提交旧表单。头像仍只在本机保存,不在此记录内。独立设备 UI、导入中断故障矩阵与跨版本兼容需另行验收;本机两个实际客户端的同改收敛已另有测试证据。
旧全局人设导入使用只读 GET `/api/settings/persona/legacy`,仅桌面模式提供,并先向 Host 验证当前 Vault。返回 available 与旧人设内容,不返回旧记录 revision,也不写入任何 Workspace 记录。UI 展示预览后,用户可以点击“填入当前表单”;这只替换可编辑内容,保留当前目标的 version/revision。最终保存沿用正常人设 PUT、Host CAS 与 journal。取消预览/关闭表单不会导入,切换 Vault 后迟到响应被丢弃,旧 SQLite 来源始终保留。没有自动清除或把读取行为作为迁移完成标记。
逻辑记录的“另存副本并采用远端”可将原始记录内容导出为 `attachments/*.txt`;文本副本作为普通附件同步,拥有独立 file_id,不作为活动设置自动应用。副本目标必须属于同步白名单;若目标本身位于记录命名空间,还必须与原始记录 kind/id/schema 一致。这些检查在持久化冲突决定之前完成,因此无效路径不占用该冲突的解决选择。
## 对话、Agent、Provider 与安装清单记录 v1
对话使用 `opennexus-records/v1/conversations/conversation_<32位小写十六进制>.json`。data 只包含 title、active_leaf、created_at_ms、updated_at_ms 和 messages;每条 message 只包含 message_id、parent_message_id、role、content、thinking、attachments、created_at_ms。消息 ID 唯一,父节点和 active_leaf 必须引用本记录中的消息;role 限 user/assistant/system,附件只保存可移植的相对引用。单条正文或思考最多 256 KiB,记录整体仍受 1 MiB 上限约束。
已结束的 Agent 历史使用 `opennexus-records/v1/agent-history/agent_run_<32位小写十六进制>.json`。data 只包含 status、input、output、model、skill_id、error_code、error_message、token_usage、created_at_ms、updated_at_msstatus 仅允许 completed/failed/cancelled。queued、running、waiting_permission 不可编码,目标设备不会恢复运行、继续工具调用或继承许可。
一般 Provider 参数使用 `opennexus-records/v1/provider-settings/provider_<32位小写十六进制>.json`。data 只包含 version、provider_type、name、base_url、default_model、enabled、capabilitiesbase_url 必须是无用户信息、查询或 fragment 的 HTTP(S) URL。credential_id、API Key、Header、令牌、请求秘密和设备凭据状态不是合法字段,目标设备需单独配置凭据。
安装清单使用 `opennexus-records/v1/extension-installations/extension_<32位小写十六进制>.json`。data 恰好包含 package_kind、package_id、source、version、sha256package_kind 限 skill/plugin/theme。该记录只表达期望安装项,不包含 enabled、trusted、permissions、granted_permissions、device_grants、package_path 或本机托管目录。目标设备必须从 source 重新获取、验证 sha256、执行本机安装审查并重新授权,逻辑记录本身不能启用扩展。
以上四类与人设、布局均默认关闭。Rust 在 journal 前、远端应用前和上传提交前重复验证信封、路径、schema 与 data;未知字段、非法 ID、超限内容和 schema≠1 均拒绝。正文属于用户主动选择同步的内容,字段白名单不按关键字删除正文;秘密仍不得进入专用配置字段、附件外普通文件或任意数据库副本。
## 可选记录范围(Workspace schema 13
人设、布局、对话、已结束 Agent 历史、一般 Provider 参数和安装清单默认仅保存在本机。用户在同步设置中逐类选择后才参与传输;选择保存在本机 Vault 数据库,不随逻辑记录上传。已有活动绑定时不得直接改变范围,必须解除绑定、调整选择、重新预览合并;预览摘要包含范围,旧范围的预览不能用于绑定新范围。
本地扫描、外部删除发现、首次对账和 outbox 捕获均排除未选择的类别。旧版本残留的可选上传作业会被归档,提交前再次核验范围。关闭选项不删除本地文件或远端历史,也不产生远端 delete。接收未选择的类别时只保存 revision 元数据并推进已处理游标,既不获取对象正文,也不应用记录或制造冲突。已有未完成的排除类别冲突决定保留,但不继续应用;需重新对账处理。
重新开启经新绑定获取固定远端快照,因此能够获取先前已跳过游标的记录,匹配的现有文件沿用初始对账身份与 CAS 规则。普通 Markdown、允许附件、任务、用户 Skill、主题和编辑器偏好保持默认同步;手动导出的 attachments/*.txt 是普通附件,不是自动生效的人设。Workspace 从 schema 12 升级前创建完整可读 SQLite 备份,保留旧 persona/layout 选择,新类别全部初始化为关闭;schema 13 以上由旧客户端以 `SCHEMA_INCOMPATIBLE` 拒绝打开,不能降级写入。