Memories / 文档

本页目录

架构与协议 v1

存储

数据库架构由 header、state 和 checkpoints 组成。除格式元数据、盐和行序列号之外,所有值都是经过认证的密文。日志也包含密文。不存在持久化的明文 FTS 索引。经过授权的普通记忆会被拆分成相互重叠、不会破坏 Unicode 的分块,并使用 Jieba 分词,然后进入内存中的 SQLite FTS5 索引。缓存以保险库策略/修订/隔离状态为键,在查询之间复用;编辑、同步和策略变更会使其失效,锁定会将其清空。SQLite 临时存储被刻意限制为仅使用内存。向量经过归一化且只在内存中缓存;明文索引和嵌入向量都不会进入备份或同步载荷。

密码派生密钥封装随机主密钥;主密钥加密状态和本地检查点。每个权限纪元都有一个保险库密钥,每个修订都有一个随机数据密钥,由其纪元密钥封装。内容经过序列化,被拆分成独立认证的 256 KiB 分块,其位置和总数绑定到关联数据中。修订签名覆盖资产 ID、父修订、策略头、墓碑标志、密文和设备证书。

变更在克隆的状态上准备,并使用 synchronous=FULL,在一个 SQLite 事务中连同加密状态和检查点一起提交。读取和执行意图在交付之前提交。因此,磁盘故障会阻止交付;意图提交后的崩溃可能留下结果未知的操作。

独占 OS 锁只允许一个遵守协作约定的本地写入者。初始化在同一目录中构建完整数据库,关闭并同步它,然后发布,且不会覆盖已有目标。暂存过程被中断不会阻止重试。每台设备都需要自己的本地存储;通过云同步客户端复制正在使用的 SQLite 文件无法提供多设备事务安全性。

身份与传输

用户 Ed25519 密钥认证设备 Ed25519 签名密钥和设备 X25519 交换密钥。仅有证书还不够:每个保险库策略都明确授权设备。设备密钥彼此独立,包括属于同一用户的设备。初始用户签名权限通过经过认证、面向接收方加密的数据包交付给获批的个人设备;设备被撤销后,该权限不能绕过保险库的设备允许列表。

信封包含格式版本、发送方设备密钥、接收方交换密钥、临时 X25519 公钥、AEAD 载荷和 Ed25519 签名。HKDF 派生传输密钥。接收方身份和临时密钥都经过认证。加入的设备使用有过期时间的签名请求,并明确固定审批设备的公钥;请求只有在成功提交后才会被消耗。

首个版本使用请求/审批字符串和文件,而非摄像头扫描二维码。指纹是完整的 SHA-256 摘要;列表中显示的缩短值绝不能作为唯一的审批检查依据。

策略与合并

保险库策略是签名的线性历史,包含成员资格、角色、设备允许列表、单调增长的设备撤销集合和纪元。角色检查使用前一条策略。普通用户可以为自己的身份授权另一台设备,而不改变成员资格。Admin 可以更改 Admin、Editor 和 Reader 的成员资格,并授权成员的设备。Admin 不能添加、删除或更改 Owner,不能撤销设备或转移所有权。Owner 可以执行这些操作。所有权转移始终保留恰好一个 Owner。

资产历史构成内容寻址的 DAG,由每一种可编辑类型共享(记忆、卡片——包括知识、大脑、人格以及受保护卡片的元数据——文件和凭据)。每次保存都会追加一个签名修订;没有独立的 wiki 专用历史存储,也没有自动裁剪。history 列出修订、作者和摘要;diff 返回两个修订之间或与当前 HEAD 比较的统一文本投影(标题、结构化元数据、正文)。文件差异省略二进制载荷;凭据的秘密正文仍不能通过读取/差异访问。一个资产有多个尚未被后续修订消耗的修订即构成冲突;解决修订列出所有可见父修订。墓碑将资产从当前视图移除,但不删除历史。

同步会验证完整的策略、修订和审计历史,然后合并到候选状态。来自已被替代纪元的未知写入会被隔离。传入的较新策略快照中不存在的本地离线写入也会被隔离。隔离只能在本地审查/重新应用,不能由不可信的传入字段清除。

权限分叉要求所有者对选定头和整个被丢弃的策略链签名。签名者必须在两个分支上都是所有者。被丢弃的策略仍可用于验证历史/隔离修订。成功解决会推进所选策略并轮换其密钥。竞争或不兼容的解决方案会按拒绝方式处理。

本地 API

service::Request 是共享的类型化操作接口,使用蛇形命名的 op 判别字段序列化。未知顶层字段会被拒绝。本地 IPC 使用带大端 32 位长度前缀的 JSON 帧(版本 1 的 Wire 包装)。可选 HTTP/WebSocket 监听和中继 RPC 在令牌认证后使用相同的 WebSocket 二进制 JSON 分帧(先 AuthFrame,再 Wire)。MCP 能力在分派之前单独检查。RunBound 的分派方式与 RemoteExec 相同:run_bound_prepare 在服务锁下授权并解密,IPC 线程在不持锁的情况下运行程序,run_bound_finish 审计结果。运行绑定存放在设备本地的 State.run_bindings,而不是保险库中。

网络传输可以承载经过令牌认证的分帧 Requests。令牌只来自标志/环境变量(绝不来自 TOML),使用恒定时间比较,并在存储锁定时擦除。远程调用方使用稳定的提权命名空间(ws:remote / relay:remote),不会继承本地 cli:{pid} 提权;通过远程连接执行解锁和 sudo 会提升该命名空间,因此 CLI 单次连接可以保留提权。桌面 HTTP/中继主机分派完整的 handle_client 接口(提权门槛与本地 IPC 相同)。移动 FFI 和 Share 保留平台拒绝列表,不会在手机上添加仅桌面支持的功能。HTTP 默认监听回环地址;绑定非回环地址要求 --allow-non-loopback 并显示警告。普通 HTTP/WS 不需要证书;--tls-cert / --tls-key 启用 HTTPS/WSS。中继 RPC 在信任边界之外使用 WSS,加上高熵房间 ID 和令牌;保险库同步保留 /v1/channel。WebUI 提供只读状态投影和受限制的普通卡片编辑器,不是 Request 传输。参见远程守护进程和 SECURITY.md。

可选的授权 Center(配置中的 [[centers]])禁止本地自行引导和本地创建 MCP 授权;注册与授权以 Center 签名票据分发,并具有明确的范围(allow_all / read / write / read_write / read_write_delete)。参见 Center。

可选的 Center 托管模式(--data DIR)在 DIR/store/ 下拥有独立的守护进程格式加密 Store,使用操作员持有的服务器密码打开。其 HTTP 内容 RPC 使用 Service 中封闭的、按保险库授权的分派器;操作局部提权覆盖受保护/私有内容和附件,但不改变守护进程的 MCP 授权。这是服务器端加密,不是客户端端到端加密。独立的管理员 HTTP 路由管理托管锁定/解锁,以及仅所有者可访问的 DIR/members.json 中持久化的命名授权。签名解锁票据只授权本地设备密码解锁;设备密钥绝不会到达仅提供票据的 Center。单个 [[centers]] 条目带有 mode = "saas" 时会让守护进程内容 RPC 使用该托管后端及其环境中的 bearer,而不提供并行本地内容路径或 MCP 能力提权。设备/管理员控制不属于授权内容接口。边界和迁移参见 Center 托管保险库。

移动 ABI 声明在 crates/ffi/memories.h 中。句柄是数字注册表 ID,不是原始 Rust 指针。每个返回字符串必须使用 memories_free 释放一次。无效 JSON 会产生通用错误,不回显输入。memories_lock_all 在进入后台时推进全局锁定代次,并抑制迟到的在途响应。注册表保存引用计数的逐句柄服务锁;网络工作不持有注册表锁或写入者锁。忙碌的句柄在下一个请求之前应用待处理锁定。移动端排除进程执行和 MCP 授权创建。

Flutter 模式选择器(mobile/lib/app_mode.dart)将 Standalone 路由到本地库,将 Center/SaaS 路由到 HostedCenterPage。这里的 Center 指带托管存储的自托管服务器;SaaS 选择固定的 https://memories.ivjn.us 端点。两者使用相同的 Rust hosted_* FFI 操作和 HostedClient;选择不会修改守护进程 TOML、注册由票据管理的设备,或在存储之间移动数据。

所选模式是非秘密偏好。Center 和 SaaS 各自在平台安全存储中保存一个 URL/令牌配置。切换会锁定本地服务、停止共享并释放托管页面/会话。进入后台会清空托管内容并使迟到响应失效;返回后需要明确重新连接。配置键和升级行为参见移动配置,面向用户的步骤参见移动指南。

桌面托盘与状态投影

Request::DesktopStatus 返回初始化/锁定状态、库和连接数量、已配置提供方类型以及同步队列数量的类型化投影。它在解锁操作分派之前运行,不克隆或提交加密状态,不触碰空闲/提权计时器,也不暴露标识符、账户摘要、内容或凭据。锁定响应不包含详情。现有 MCP 允许列表拒绝此请求。

CLI 库承载本地仪表盘和 Windows/macOS 托盘。memories tray 和独立的 memories-tray 启动器都使用原生主线程事件循环。HTTP 服务器和有界状态探测工作线程独立运行,因此忙碌的守护进程无法阻塞托盘或浏览器。探测器通过正常的 OS 认证 IPC 调用同目录 CLI,并设置截止时间和子进程清理。HTTP 接口提供嵌入资源、固定状态投影和明确的普通卡片/类型编辑允许列表。它不能解锁、提权、泄露秘密或分派任意服务请求。令牌和浏览器来源边界参见 SECURITY.md,另见桌面使用。

检索与模型编排

search::Cache 最多持有两个保险库索引,每个索引限制为 10,000 个分块、16 MiB 源文本/元数据和 32 MiB 向量。它在索引之前排除私有、凭据、文件、已删除、隔离和冲突内容。BM25 对标题/正文/元数据的权重为 5/1/2。向量搜索使用余弦相似度;混合搜索使用倒数排名融合组合两种排名。默认重排序在有界候选集中提升查询覆盖率和精确短语匹配。配置的模型端点可以执行学习式重排序。

Retrieve、RagIndex、RagStatus 和 RagAsk 保留保险库授权和 MCP 授权范围。IPC 在服务互斥锁之外执行模型工作,并在外部工作前后及交付之前重新检查解锁代次、授权有效性、访问权限和源修订戳。签名意图先于推理/导出;审计记录成功/失败和交付的源 ID,不记录提示词或密钥。源资产/修订引用使用共享的短 ID 投影;分块哈希和 [S1] 引用标签不是资产 ID。

rag::Runtime 延迟加载原生 ONNX 推理和固定的 EmbeddingGemma Q4 资源,或调用配置的 Gemini/兼容嵌入 URL。它还支持配置的生成和重排序 URL、通过环境引用的 API 密钥、代理和重试。原生模型文件是公开模型数据;加密用户记录绝不会成为模型权重。检索文本在生成提示词中是不可信数据,返回的引用 ID 会针对所选来源验证。这验证的是引用,不是模型生成文字的事实正确性。

移动 FFI 获得改进的词法搜索,并支持 BM25 retrieve。基于模型的操作目前使用已配置的桌面守护进程;本版本没有为 Flutter 添加模型配置或模型推理 UI。

桌面密码学作业

jobs::Job 描述工具、固定的输入文件/目录树、凭据绑定、有序步骤、清理和新的输出路径。CLI 的 crypto 配方为 S/MIME/OpenPGP 和 Android/iOS 签名构建此计划。审批在解析短 ID 之前检查,因此摘要准确绑定调用方看到的引用。服务检查本地提权和保险库访问,只解析凭据资产,提交执行意图,然后调用共享核心运行器。

运行器重新检查工具、复制并验证输入、在私有工作区中实体化凭据、不使用 shell 运行命令、抑制输出,并且只发布成功的产物。占位符展开只替换路径,不递归解释路径内容。MCP 允许列表和移动 FFI 拒绝进程作业。现有 run 语义不变。

AI 客户端技能

memories inject 是仅 CLI 支持的安装操作。skills.rs 将带版本的 crates/cli/skills/memories/SKILL.md 嵌入可执行文件,并将其安装到文档指定的 Codex、OpenCode 或 Claude Code 发现目录。它在创建数据主目录之前运行,绝不打开存储或调用守护进程。用户和项目范围共享同一指令文本;不会向其中插入存储路径、保险库 ID、授权令牌或秘密材料。

安装器预检所有目标,保留相同文件,内容不同时要求 --force,拒绝链接形式的技能文件/目录,并通过目标目录中已同步的临时文件发布每个文件。新文件发布时不会覆盖并发创建的文件。这是逐文件原子性,不是跨客户端事务。现有同级资源和客户端配置会保留。技能通过现有 MCP 或 CLI 接口路由普通搜索、带引用的检索、配置的回答和写入;不会增加授权能力。

云授权

桌面 CLI 使用 PKCE、回环回调和固定提供方端点执行浏览器 OAuth。CloudBegin 在 sudo 下创建具有过期时间、绑定调用方/提供方/客户端的票据。网络请求在守护进程之外运行;CloudComplete 在提交之前检查票据和当前授权。锁定、退出登录和重启会使待处理票据失效,防止迟到结果恢复登录。

State.cloud_accounts 在本地设备上加密保存令牌。状态只返回账户元数据;备份、配对、保险库同步和 MCP 排除令牌。登录不会建立保险库成员资格。

云对象同步

State.cloud_sync 在同一个加密状态 blob 中保存持久化 outbox/inbox 回执。CloudSyncEnqueue 在任何传输 I/O 之前写入精确的序列化信封字节和签名描述符。对象 ID 是这些字节的 SHA-256;重试复用它们。CloudSyncWork 返回密文和列表游标,不返回 OAuth 令牌。sync::execute 不持有服务互斥锁,执行有界 put/get/list。CloudSyncAck 检查进程本地会话代次,然后一起提交上传标志、发现的 ID、下载字节和保险库合并。先于对象到达的描述符保持待处理状态。锁定、解锁和退出登录递增会话,因此迟到确认无法导入。memories cloud sync 请求守护进程通过 OneDrive 或 Google Drive 应用文件夹刷新令牌、上传和下载;HTTP 在写入者锁之外运行。令牌绝不通过 IPC 返回。测试也使用 MemoryTransport 和回环 Graph/Drive 测试夹具。已完成队列载荷压缩为哈希回执和逐提供方/接收方发布戳。回执保留在本地,不删除云历史。缺失对象按照持久化的指数延迟重试;待处理容量会对列表施加背压。提供方工作线程按账户串行执行,并拒绝失效会话的刷新/导入结果。IPC 分派最多使用 16 个并发连接。

参见同步设计和授权指南。

远程会话

桌面守护进程拥有 SSH、FTP、TCP、MySQL、Redis 和 PostgreSQL 连接。RemoteOpen 验证凭据主机允许列表,记录连接意图,然后在写入者锁之外连接。SSH 使用密码凭据或导入的 OpenSSH 私钥认证,也可以额外使用 OpenSSH 证书凭据。CredentialGenerate 在 sudo 下创建 SSH 密钥和 TLS 证书;私钥作为凭据存储,绝不返回。CredentialPublic 从该存储材料派生 SSH 公钥或 PEM 证书。数据库会话默认启用 TLS,并可将可选 PEM CA 凭据(tls_ca)附加到客户端信任存储。必须验证主机名;尚未实现证书固定。每条指令作为票据提出,其指纹覆盖会话、序列和规范化指令。RemoteApprove 只能使用一次,并绑定已提权的调用方;RemoteExec 仅在现有会话上运行该票据。锁定、空闲到期和退出登录会丢弃活动连接和待处理票据。文件操作使用 FTP;SSH 和数据库会话接受 exec/query。 ProxyStart 在执行与 RemoteOpen 相同的凭据和主机允许列表检查后绑定回环监听器。本地客户端无需秘密即可连接;守护进程持有上游 SSH 或数据库认证。proxy.start 在绑定之前记录连接意图;proxy.stop 记录停止意图。已绑定套接字直接转移到 Tokio,不存在关闭/重新绑定窗口。 PostgreSQL SSLRequest 和 MySQL SSLRequest 先于 TLS 升级;Redis 使用直接 TLS。PostgreSQL SCRAM 同时检查 nonce 和服务器签名。MySQL 支持 native 和 caching-SHA-256 认证,包括插件切换;完整密码交换要求 TLS。数据库握手有 30 秒截止时间、64 KiB 数据包边界和每个监听器 16 个客户端的限制。 本地数据库连接段是明文,登录/数据库启动字段不会覆盖配置的上游身份。本地客户端在该账户权限内授权不受限制的远程指令。回环不提供逐用户认证。锁定、保险库空闲到期、主动停止和服务销毁会取消监听器和活动会话。代理最长存活 30 分钟;流量不会延长保险库解锁时间。MCP 不能管理代理。 服务拥有每个代理工作线程,并在停止/锁定时等待它结束,因此成功响应不会与仍在转发指令的连接发生竞态。

配置文件执行

memories file exec 将现有已授权 Read 请求与 CLI 本地文件实体化及进程监督组合。它不引入新的服务请求、文件类型、密码学或移动/MCP 执行权限。守护进程使用现有读取路径审计文件交付,然后 CLI 运行前台子进程,不占用服务锁。CLI 在直接子进程退出之前拥有临时明文快照;后续保险库变更或锁定不会改变/撤销该已交付快照。

恢复

备份在独立的 Argon2id/AEAD 信封下包含签名本地状态和秘密恢复材料。恢复生成新的设备密钥并创建独立保险库 ID,保留当前内容和冲突头。原始签名历史保留在加密只读归档中。恢复不会声称历史共享成员资格仍然有效。仅设备保险库、云授权和云 outbox/inbox 回执被排除;每台恢复的设备分别登录云提供方。

卡片与安全投影

Kind::Card 共享普通记忆授权,类型化元数据存储在加密修订内。CardLink 添加同一保险库内的有向边。图只投影当前、明确的可见节点,以及两个端点都可见的边。搜索使用上述有界内存 FTS5 缓存,覆盖普通正文、标签/来源和允许列表中的受保护卡片元数据。凭据正文绝不进入此索引。搜索/列表/图暴露安全的 summary 投影;卡片读取保留不透明的凭据引用,但绝不解析其值。

protected_card 验证账户/env/令牌/TOTP/HOTP 输入(包括 otpauth URI 导入),将凭据及其卡片写入同一个候选状态,并一起提交。通用 put 不能附加或替换受保护引用。秘密操作解析当前凭据修订并验证其类型。otp_code 实现 RFC 6238(TOTP)和 RFC 4226(HOTP),在同一次提交中推进 HOTP 计数器,并在返回验证码之前记录交付意图。HTTP 请求头构造将 bearer/cookie 值标记为敏感,禁用重定向并抑制响应。

记忆和卡片内容还可以携带从稳定标签映射到规范凭据资产 ID 的 secrets 映射。protect_secret 将正文中的精确匹配替换为 {{secret:LABEL}},并在同一个事务中创建凭据。通用 put 要求现有映射保持不变,不能创建或重定向它们。CLI 和移动编辑会沿用现有映射。MCP card_write 省略此映射,因此更新包含秘密引用的卡片会验证失败。客户端投影只缩短类型化凭据 ID,保持正文标记稳定。备份恢复保留明确的资产 ID,因此映射在恢复后仍然有效。credential_copy 直接解析标签、受保护卡片或凭据,要求本地提权,并在向剪贴板客户端返回 UTF-8 明文之前提交审计意图。MCP 拒绝这两个新操作。任何通用读取、搜索或图操作都不会解析秘密。签名历史正文不会因替换而改变;参见 SECURITY.md 中的 wiki 和剪贴板限制。

服务仅向已授权的本地复制调用方返回 {"secret":"..."}。CLI 在 clipboard.rs 中消费此响应,并且仅在剪贴板写入成功后,才在正常输出信封中返回 {"copied":true}。移动端使用 Flutter 剪贴板通道,并在交付之前检查其生命周期代次。审计记录 credential.copy 及其 delivery_intent,而非 OS 剪贴板成功回执。参见使用指南。

State.local_vaults 在加密本地状态中标记仅设备保险库。local_config 将客户端收集的版本和目录元数据记录在其中一个保险库中。设备审批会过滤这些保险库;直接成员导出和同步被拒绝;store::backup 从导出快照中移除它们。明确的资产 ID 会在新保险库恢复后保留,从而保留图和卡片到凭据的引用,而原始签名历史继续归档保存。