本地记忆与加密存储
“记忆”用于保存个人文字、结构化选项、图片与文件,核心读写、搜索和模板推荐在设备本机完成,不要求先登录。账号退出不会自动清除记忆;当前没有跨设备自动同步或通用导入恢复能力。
模型与数据流
iOS 的 MemoryEntry 包含 UUID、标题、正文、附件数组、创建和更新时间、MemoryKind、字段值字典以及可选的 generatedSchema。附件元数据记录标识、原名称、存储名称、字节数与图片标记。视图将编辑结果交给 MemoryStore.save,存储层清理字段、加密新增附件、原子写索引,成功后再删除不再引用的旧附件;失败时清理本次新增文件。
Android 使用 JSON 记录,常见键包括 id、title、body、kind、时间戳及 attachments,模板字段直接位于记录对象中。MemoryScreens 处理编辑和附件选择,AppState 更新记录列表,LocalData 写入加密索引。两端转出容器相同,但内部记录字段与时间表示不同,不应假设可以直接互换。
固定主题包括自由记录、医疗健康、签证、证件、预约和订阅;宠物接种有专用字段。签证目的地使用本地国家地区目录,类别引导不自动推算有效期。医疗字段见医疗健康记录。
限制与兼容
| 内容 | 当前约束 |
|---|---|
| 标题 | 最多 80 字符,空标题可由正文首行或模板名补全 |
| 正文 | 最多 20,000 字符 |
| 单个附件 | 最多 20 × 1024 × 1024 字节 |
| 每条附件数 | 最多 20 个 |
| 每条附件总量 | 最多 50 × 1024 × 1024 字节 |
| 一般字段 | 最多 200 字符 |
| 诊断结果、用药与医嘱 | 各最多 2,000 字符 |
这些限制由各端原生字符串截取实现,不能理解为跨语言一致的字节长度。iOS 存储层拒绝空附件;Android 文档选择路径允许 0 字节附件,扩展时需保留这一实现差异。仅选择模板但没有标题、正文、字段或附件,不会保存空记忆。
旧版兼容由两端分别处理:iOS 缺少主题字段时按自由记录解码,并支持旧明文库迁移;Android 保留旧键别名,同主题编辑只替换当前可见字段,保留未展示的历史字段。切换主题可能清空原主题选项,界面先要求确认。
加密与错误保护
iOS 采用 CryptoKit AES-GCM,加密文件以 SLMV2 标识版本;索引与每个附件使用不同 purpose 作为认证附加数据。随机 256 位密钥放入 Keychain,属性为设备解锁时可用且仅限本设备。索引与附件使用 complete 文件保护。旧明文数据先在暂存目录加密并回读核验,再移入正式 vault,随后清理旧文件。
Android 采用 Android Keystore 生成的 256 位 AES 密钥,以 AES/GCM/NoPadding 加密索引和附件,文件保存 IV 与密文,GCM 标签为 128 位;写入采用临时文件、同步和重命名。两端文件格式并不相同。
本机记忆使用设备级随机密钥,不由 Account 密码派生,也不按聊天账号隔离。同一安装中的不同聊天账号访问同一本机记忆库;修改 Account 密码、退出或切换聊天账号,都不会删除记忆或重新加密其内容。账号身份验证与设备上的记忆存储是独立边界。
索引损坏、认证失败或密钥不可用时,iOS 设置 loadError,Android 设置 memoryLocked,均禁止覆盖原记忆。不能把读取失败当成一个可写的空库,也不应引导用户通过卸载处理读取错误。卸载或清除应用数据会失去私有记录;密钥与文件需共同可用。
搜索与本机推荐
搜索覆盖标题、正文、主题、结构化字段值和附件名。空白分隔的多个词必须全部命中,但可命中不同字段;不读取附件正文,不是 OCR 或向量检索。
推荐最多读取 2,000 字符输入。Android 使用关键词和固定字段;iOS 在 iOS 26 及以上、系统模型可用时尝试 FoundationModels 生成主题与字段,失败则回退到本机基础选项。明确医疗内容直接采用稳定医疗字段,宠物疫苗保留专用识别。模型结果是待用户确认的记录结构,不会自动填入未提供的个人信息。
聊天“保存到记忆”提取消息正文;iOS 明确排除位置消息,并标记未捕获的媒体。不能将保存文字理解为自动备份整段聊天或附件。
模板字段与识别后的编辑
| 模板 | 固定字段 ID |
|---|---|
| 医疗健康 | healthRecordType, healthSubject, healthRecordDate, medicalInstitution, departmentDoctor, medicalResult, medicationInstructions, followUpOn |
| 签证 | destination, visaType, status, entries, validFrom, validUntil, reference |
| 证件 | documentType, number, issuedOn, expiresOn |
| 预约 | subject, location, startsAt, status |
| 订阅 | service, plan, amount, renewsOn, status |
| 宠物接种 | pet_species, pet_name, pet_life_stage, pet_age, dose_number, vaccine_valency, vaccine_name, vaccinated_on, next_vaccination |
| 通用推荐 | subject, date, place, status, remark |
自由记录没有必填模板字段。签证的 destination 保存目的地值,界面可解析为国家或地区名称;签证类型和有效期由用户按实际文件确认,类型候选不会替用户推算截止日期。预约的 startsAt 含本地时间,日期型字段只保存日历日期。订阅金额仍为用户输入字符串,没有币种换算或自动扣款。宠物针次、联数与下次日期也只是记录,不构成接种计划或提醒。
iOS 手动医疗模板使用 kind=health;从识别建议应用生成 schema 时,编辑器使用 kind=note 加 generatedSchema 保存主题与选项。需要寻找全部医疗主题时不能只筛原始 kind。Android 的固定医疗模板保持 health,开放主题则使用 suggested 与 suggestedCategory。原始结构和字段类型见本机数据格式。
新建 iOS 自由记忆时,至少两个字符输入会触发延迟建议:少于五字符等待约 2.5 秒,其他约 1.5 秒;自动建议有次数和输入状态保护,页面离开会取消请求。建议出现不会保存记录,用户仍需确认选项并保存;修改输入后的迟到识别结果会被核对后丢弃。Android 通过按钮按文字推荐当前固定或通用选项。
iOS 重新应用生成 schema 时,按稳定字段 ID、相同显示标题及少量签证旧标题别名搬移已有值;这不是任意语义迁移。手动更换类型会确认清除原选项,正文和附件保留。Android 同主题编辑保留未展示旧键,更换类型则清理相关选项。业务扩展应稳定字段 ID,分别测试“清空字段”“换类型”“重选建议”和“编辑旧记录”,不能只验证新建成功。
附件预览需临时解密为明文。iOS 使用受保护预览文件并在结束后清理;Android 在缓存中写明文,通过 FileProvider 临时读权限交给用户选择的查看应用,离开详情时尽力删除预览文件。临时文件清理不撤回外部应用已经复制的内容。图片、文件的读取和导出也会占内存,单条附件上限不等于任意数量记录都能一次处理。
转出与验证
以下描述客户端已编写的转出实现契约。当前作为维护基线的 Account 主目录版本尚缺 /api/memory-export/redeem 路由,跨服务真实闭环仍待核对和验收,不能将客户端入口或本地文件生成代码视为当前可用的完整转出服务。
按该契约,转出需要登录并通过 Account 的一次性许可流程。客户端发送的是验证状态和票据,记忆正文与附件由本机生成;具体身份校验见认证。本地输出格式定义为明文 JSON,容器含 format=cn.thestarline.memory-export、version=1、entries;每项含原记录和按附件 ID 编码的原始附件数据。
iOS 在验证成功后创建受保护临时文件,交给系统分享页,并在结束后清理;Android 在授权和保存位置选择后生成临时文件,流式复制到目标 URI,取消或失败时清理未完成输出。转出文件中的 Base64 不是加密,保存位置和接收方由用户选择。
仓库测试覆盖重开、字段限制、损坏保护、加密附件与完整转出等路径。本轮未重跑测试,也未完成真实账号多因素转出验收;完整容器、平台取消阶段和清理限制见记忆转出,回归范围见测试与安全与隐私。源码主要位于 iOS MemoryStore/MemoryLocalRecognizer/MemoryEditorView 与 Android MemoryTemplates/MemoryScreens/AppData。