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

19 KiB
Raw Permalink Blame History

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 /devicesDELETE /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_DAMAGEDrestart_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=1kind=taskiddata。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.jsonkind 为 theme_settings、id 为 appearance;编辑器偏好使用 opennexus-records/v1/preferences/editor.jsonkind 为 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位小写十六进制>.jsonkind 为 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.jsonschema=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 拒绝打开,不能降级写入。