docs: add secure persistence architecture and release pack
This commit is contained in:
parent
c7d15d342d
commit
3798e2f45b
211
docs/architecture/secure-local-persistence-architecture.md
Normal file
211
docs/architecture/secure-local-persistence-architecture.md
Normal file
@ -0,0 +1,211 @@
|
||||
# Secure Local Persistence Architecture
|
||||
|
||||
## 目标
|
||||
|
||||
这次补丁保持现有 UI 不变,只重设计 `XWorkmate` 的本地配置与任务会话持久层,满足两个约束:
|
||||
|
||||
- 本地配置和任务会话必须能跨重启、跨覆盖安装恢复。
|
||||
- 持久化以前提 `secure storage` 为本地信任根,避免把可恢复状态明文落盘。
|
||||
|
||||
核心结论:
|
||||
|
||||
- `FlutterSecureStorage` 仍是长期 secret 的主存储。
|
||||
- 本地配置和任务会话不直接明文写入 SQLite / JSON,而是先用本地状态密钥加密后再落盘。
|
||||
- 本地状态密钥本身必须优先保存在主 secure storage,不再把它当成普通可降级 secret。
|
||||
|
||||
## Trust Boundary
|
||||
|
||||
需要明确区分 3 类状态:
|
||||
|
||||
1. 用户输入的高敏感 secret
|
||||
- Gateway shared token
|
||||
- Gateway password
|
||||
- AI Gateway API key
|
||||
- Vault token
|
||||
|
||||
2. 可恢复但不应明文落盘的本地状态
|
||||
- `SettingsSnapshot`
|
||||
- Assistant 任务线程记录
|
||||
- 最后活动线程
|
||||
- 本地恢复 backup
|
||||
|
||||
3. 仅调试或测试环境可接受的替代路径
|
||||
- 注入式 secure storage client
|
||||
- 临时文件型 secure storage fallback
|
||||
|
||||
边界规则:
|
||||
|
||||
- 第 1 类状态优先进入 secure storage;secure storage 超时或异常时,可进入持久化 fallback 文件,但绝不退化成“仅内存”。
|
||||
- 第 2 类状态不直接进入 `SharedPreferences` 或明文 SQLite;必须先 sealed。
|
||||
- 第 3 类路径只用于 debug / test,不进入 release 行为。
|
||||
|
||||
## 架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Gateway / Settings Form"] --> B["SettingsController"]
|
||||
C["Assistant Thread State"] --> D["AppController"]
|
||||
B --> E["SecureConfigStore"]
|
||||
D --> E
|
||||
|
||||
E --> F["Primary Secure Storage<br/>FlutterSecureStorage"]
|
||||
E --> G["Local State Key<br/>xworkmate.local_state.key"]
|
||||
G --> H["AES-GCM Seal / Unseal"]
|
||||
|
||||
H --> I["SQLite config-store.sqlite3"]
|
||||
H --> J["Durable state files<br/>settings-snapshot.json<br/>assistant-threads.json"]
|
||||
H --> K["assistant-state-backup.json<br/>schemaVersion=2 / sealedState"]
|
||||
|
||||
E --> L["Secure secret fallback files<br/>gateway-auth/*"]
|
||||
```
|
||||
|
||||
## 存储分层
|
||||
|
||||
### 1. Primary Secure Storage
|
||||
|
||||
用途:
|
||||
|
||||
- 保存 Gateway token / password / AI Gateway API key / Vault token
|
||||
- 保存本地状态密钥 `xworkmate.local_state.key`
|
||||
|
||||
关键要求:
|
||||
|
||||
- 主路径仍然是 `FlutterSecureStorage`
|
||||
- 本地状态密钥不允许再走“通用 secret fallback”
|
||||
- 如果主 secure storage 不可用,不允许把本地状态密钥退化成普通文件常态
|
||||
|
||||
### 2. Sealed Local State
|
||||
|
||||
本地配置和任务会话的持久化结构统一改为:
|
||||
|
||||
- `storageFormat = xworkmate.sealed.local-state.v1`
|
||||
- `nonce`
|
||||
- `cipherText`
|
||||
- `mac`
|
||||
|
||||
加密方式:
|
||||
|
||||
- AES-GCM 256
|
||||
- 每次写入使用新的随机 nonce
|
||||
- AAD 绑定存储 key,避免跨 key 错读
|
||||
|
||||
当前覆盖对象:
|
||||
|
||||
- `xworkmate.settings.snapshot`
|
||||
- `xworkmate.assistant.threads`
|
||||
- `assistant-state-backup.json`
|
||||
|
||||
### 3. Durable Recovery Files
|
||||
|
||||
当 SQLite 不可用时,仍需保证本地状态可以恢复。为此保留两类耐久化文件:
|
||||
|
||||
- `settings-snapshot.json`
|
||||
- `assistant-threads.json`
|
||||
|
||||
注意:
|
||||
|
||||
- 文件名虽然保持旧风格,但内容已改为 sealed payload,不再是明文 JSON。
|
||||
|
||||
### 4. Assistant Backup
|
||||
|
||||
`assistant-state-backup.json` 升级到 schema v2:
|
||||
|
||||
- 用 `sealedState` 保存整体恢复快照
|
||||
- 不再把 settings / threads 明文拼进 backup
|
||||
|
||||
这样做的目的:
|
||||
|
||||
- 避免备份文件成为最容易泄露的明文副本
|
||||
- 保持“数据库损坏时仍可恢复”的能力
|
||||
|
||||
## 写入流程
|
||||
|
||||
### SettingsSnapshot
|
||||
|
||||
1. `SettingsController` 生成新的 `SettingsSnapshot`
|
||||
2. `SecureConfigStore.saveSettingsSnapshot()` 进入本地状态写队列
|
||||
3. 读取或生成 `xworkmate.local_state.key`
|
||||
4. 先 sealed,再写入 SQLite / durable file / backup
|
||||
|
||||
### Assistant Threads
|
||||
|
||||
1. `AppController` 更新线程记录
|
||||
2. 持久化进入 `_assistantThreadPersistQueue`
|
||||
3. `SecureConfigStore.saveAssistantThreadRecords()` 串行 sealed 写入
|
||||
4. 同步刷新 SQLite / durable file / backup
|
||||
|
||||
这么做是为了避免异步写晚到,把旧线程快照覆盖新状态。
|
||||
|
||||
## 读取与恢复流程
|
||||
|
||||
恢复顺序:
|
||||
|
||||
1. 优先读 SQLite
|
||||
2. SQLite 不可用时读 durable state files
|
||||
3. 若主状态缺失,再读 `assistant-state-backup.json`
|
||||
4. 若读到的是旧明文格式,则立即迁移为 sealed 格式
|
||||
|
||||
迁移原则:
|
||||
|
||||
- 兼容旧明文快照,避免升级后直接丢历史
|
||||
- 一旦成功恢复,就把旧格式重写成 sealed 新格式
|
||||
- legacy `SharedPreferences` 里的本地状态在迁移后会被清理
|
||||
|
||||
## Secure Secret Fallback
|
||||
|
||||
Secret fallback 仍然保留,但语义变了:
|
||||
|
||||
- 用于 Gateway token / password / API key 等长期 secret 的持久化兜底
|
||||
- 不再因为一次超时就退化成“仅内存”
|
||||
- 这样即使 secure storage 一时不可用,重启后 secret 仍能恢复
|
||||
|
||||
约束:
|
||||
|
||||
- `xworkmate.local_state.key` 不在通用 fallback 白名单里
|
||||
- 对旧版遗留的 `local-state-key.txt`,启动时做一次迁移,成功后删除
|
||||
|
||||
## Clear 行为
|
||||
|
||||
`clearAssistantLocalState()` 只清理:
|
||||
|
||||
- 本地 settings snapshot
|
||||
- 本地 assistant thread records
|
||||
- durable state files
|
||||
- assistant backup
|
||||
|
||||
不会误删:
|
||||
|
||||
- 已保存的 Gateway token / password
|
||||
- AI Gateway API key
|
||||
- Vault token
|
||||
- 其他 secure refs
|
||||
|
||||
## Debug / Test 策略
|
||||
|
||||
为了让测试稳定运行,新增了可注入的 secure storage 层:
|
||||
|
||||
- `SecureStorageClient`
|
||||
- `FlutterSecureStorageClient`
|
||||
- `FileSecureStorageClient`
|
||||
- `MemorySecureStorageClient`
|
||||
|
||||
策略是:
|
||||
|
||||
- release:使用真实 `FlutterSecureStorage`
|
||||
- debug / test:允许走注入式或文件型 secure storage,保证单测和回归可跑
|
||||
|
||||
这不会改变 release 的安全边界。
|
||||
|
||||
## 与现有 UI 的关系
|
||||
|
||||
这次补丁不改:
|
||||
|
||||
- Gateway 设置页结构
|
||||
- Assistant 任务线程 UI
|
||||
- 模型、skills、入口按钮布局
|
||||
|
||||
变化只在持久层和恢复链路:
|
||||
|
||||
- 重启后不再因为 secure storage 一次超时而丢本地配置
|
||||
- 覆盖安装后本地配置与任务会话仍可恢复
|
||||
- 本地 snapshot / backup 不再以明文保存
|
||||
181
docs/cases/secure-local-persistence-postmortem.md
Normal file
181
docs/cases/secure-local-persistence-postmortem.md
Normal file
@ -0,0 +1,181 @@
|
||||
# Secure Local Persistence Postmortem
|
||||
|
||||
## 问题摘要
|
||||
|
||||
用户现场反馈很直接:
|
||||
|
||||
- 当前会话里 Gateway 可以正常连接
|
||||
- App 一重启,本地配置和已保存凭证丢失
|
||||
- `Gateway 访问` 页重新出现 `gateway token missing`
|
||||
|
||||
这不是单点 bug,而是持久层设计里连续几处降级路径叠加后的结果。
|
||||
|
||||
## 用户可见症状
|
||||
|
||||
### 1. 重启后网关凭证丢失
|
||||
|
||||
表现:
|
||||
|
||||
- token / password 在当前会话内可用
|
||||
- 退出再打开后不可用
|
||||
- 首次连接提示重新输入 shared token
|
||||
|
||||
### 2. 本地配置或任务会话恢复不稳定
|
||||
|
||||
表现:
|
||||
|
||||
- settings snapshot 或 assistant threads 在某些路径下恢复失败
|
||||
- backup 虽然存在,但仍可能是明文旧格式
|
||||
|
||||
### 3. 明文本地状态残留
|
||||
|
||||
表现:
|
||||
|
||||
- 旧版 `SharedPreferences` 和 SQLite 中存在明文 settings / threads
|
||||
- backup 文件也可能保留明文副本
|
||||
|
||||
## 根因
|
||||
|
||||
## 根因 1:对 `FlutterSecureStorage` 强制套了 400ms 超时
|
||||
|
||||
旧逻辑:
|
||||
|
||||
- secure storage 读写只要超过 `400ms`,就视为失败
|
||||
- 一旦失败,直接退化成“仅内存”
|
||||
|
||||
结果:
|
||||
|
||||
- 当前进程内看起来一切正常
|
||||
- 因为值实际上没持久化,进程退出后凭证全部丢失
|
||||
|
||||
这是这次“重启后 token 消失”的直接根因。
|
||||
|
||||
## 根因 2:secure storage 失败时降级策略设计错了
|
||||
|
||||
旧策略把“可恢复 secret”误当成“会话临时缓存”处理:
|
||||
|
||||
- token / password / API key 没写进 durable fallback
|
||||
- 只保存在进程内存
|
||||
|
||||
这个策略对调试场景看似友好,但对桌面 App 的真实使用是灾难性的,因为用户天然预期“已经保存”的 secret 会跨重启存在。
|
||||
|
||||
## 根因 3:legacy prefs 迁移把明文直接写回了主存储
|
||||
|
||||
迁移链路里存在一个关键缺口:
|
||||
|
||||
- 从 `SharedPreferences` 读取到旧版明文 settings / threads
|
||||
- 直接调用数据库写入
|
||||
- 没有经过 sealed local state
|
||||
|
||||
结果:
|
||||
|
||||
- 用户完成升级后,本地状态仍可能继续以明文形式存在 SQLite
|
||||
- 旧的 pref key 也没有被及时清理
|
||||
|
||||
这让“升级到新版本后自动变安全”的承诺失效了。
|
||||
|
||||
## 根因 4:本地状态密钥也被允许走普通 fallback
|
||||
|
||||
旧版把 `xworkmate.local_state.key` 当成普通 secret 处理。
|
||||
|
||||
结果:
|
||||
|
||||
- 一旦它掉进 fallback 文件,secure storage 就不再是本地状态加密的真正前提
|
||||
- 架构上变成“有 secure storage 更好,没有也能常态运行”
|
||||
|
||||
这违背了本次补丁要建立的安全模型。
|
||||
|
||||
## 根因 5:线程状态异步保存存在覆盖竞态
|
||||
|
||||
Assistant 线程会话是异步落盘的。旧逻辑没有串行 flush:
|
||||
|
||||
- 线程 A 的旧快照可能在稍后写入
|
||||
- 覆盖线程 B 或更新后的新状态
|
||||
|
||||
在加密封装增加写入成本后,这个竞态更容易暴露。
|
||||
|
||||
## 修复策略
|
||||
|
||||
### 1. secure storage 不再 400ms 即判死刑
|
||||
|
||||
- 超时提高到 `5s`
|
||||
- 对真实 `FlutterSecureStorage` 保留超时保护
|
||||
- 对测试注入 client 不套这层超时
|
||||
|
||||
### 2. secure storage 失败时改为 durable fallback,而不是仅内存
|
||||
|
||||
- Gateway token
|
||||
- Gateway password
|
||||
- AI Gateway API key
|
||||
- Vault token
|
||||
|
||||
这些 secret 在 secure storage 异常时会写入持久化 fallback,保证跨实例恢复。
|
||||
|
||||
### 3. 本地配置和任务会话统一 sealed
|
||||
|
||||
对以下状态统一改为 AES-GCM sealed payload:
|
||||
|
||||
- `SettingsSnapshot`
|
||||
- Assistant thread records
|
||||
- `assistant-state-backup.json`
|
||||
|
||||
目标是消除明文 SQLite / 明文 JSON backup。
|
||||
|
||||
### 4. legacy 明文状态迁移时立即重写并清理旧 pref
|
||||
|
||||
新逻辑:
|
||||
|
||||
- 读旧 pref
|
||||
- 若目标存储不存在,则按 sealed 路径写入
|
||||
- 写入成功后删除旧 pref key
|
||||
|
||||
这样升级后不会继续遗留明文主副本。
|
||||
|
||||
### 5. 本地状态密钥升级为 primary secure storage only
|
||||
|
||||
`xworkmate.local_state.key` 现在的规则是:
|
||||
|
||||
- 必须优先保存在主 secure storage
|
||||
- 不再纳入普通 secure fallback 白名单
|
||||
- 对旧版 `local-state-key.txt` 仅做一次迁移,随后删除
|
||||
|
||||
### 6. Assistant 线程持久化改为串行队列
|
||||
|
||||
新增线程持久化 queue 和 flush 机制,保证:
|
||||
|
||||
- 新状态不会被晚到的旧写入覆盖
|
||||
- clear / send / view-mode 切换前可以先 flush
|
||||
|
||||
### 7. dispose 后的异步通知保护
|
||||
|
||||
`SettingsController` 新增 dispose guard,避免恢复链路异步完成后向已销毁对象 `notifyListeners()`。
|
||||
|
||||
## 为什么旧方案会失效
|
||||
|
||||
旧方案的问题不在“没加密”,而在于它把三件不同的事混在了一起:
|
||||
|
||||
- 当前请求是否可用
|
||||
- 是否已经持久化
|
||||
- 是否已经安全持久化
|
||||
|
||||
一旦 secure storage 稍慢,系统就会把“当前连接可继续”错误地当成“数据已经保存”,这正是桌面应用里最危险的误导。
|
||||
|
||||
## 回归防线
|
||||
|
||||
这次新增的回归覆盖重点包括:
|
||||
|
||||
- secure storage 超时后 secret 仍能跨实例恢复
|
||||
- SQLite 不可用时,sealed 的 settings / threads 仍能恢复
|
||||
- plaintext local state 能迁移为 sealed storage
|
||||
- legacy `local-state-key.txt` 能迁移到主 secure storage 并被清理
|
||||
- backup 文件不再泄露明文 settings / threads
|
||||
|
||||
## 后续约束
|
||||
|
||||
后续所有涉及本地状态持久化的修改,都必须继续满足:
|
||||
|
||||
- `.env` 仍是预填,不是持久化真值
|
||||
- 当前用户发起连接时可直接用表单值握手,不依赖 secure-store 回读
|
||||
- local state 不得重新落回 `SharedPreferences` 明文
|
||||
- backup 不得重新变成明文副本
|
||||
- 不能再让 `xworkmate.local_state.key` 走常态文件 fallback
|
||||
117
docs/releases/2026-03-22-secure-persistence-release-update.md
Normal file
117
docs/releases/2026-03-22-secure-persistence-release-update.md
Normal file
@ -0,0 +1,117 @@
|
||||
# 2026-03-22 Secure Persistence Release Update
|
||||
|
||||
## 摘要
|
||||
|
||||
这次补丁修复的是一个发版级问题:
|
||||
|
||||
- `XWorkmate.app` 在某些机器上重启后会丢失本地 Gateway 配置和已保存凭证
|
||||
- Assistant 本地任务线程和恢复快照的持久化链路存在明文残留和竞态风险
|
||||
|
||||
本次发布不改 UI,只修正持久层与恢复链路。
|
||||
|
||||
## 用户可感知变化
|
||||
|
||||
### 1. 重启后本地配置不应再消失
|
||||
|
||||
修复后:
|
||||
|
||||
- Gateway host / port / TLS 等本地配置继续恢复
|
||||
- 已保存的 shared token / password 不再因为一次 secure storage 超时而只留在内存里
|
||||
|
||||
### 2. 覆盖安装后本地状态仍应保留
|
||||
|
||||
修复后:
|
||||
|
||||
- `/Applications/XWorkmate.app` 覆盖安装不会清掉本地配置和任务会话
|
||||
- Assistant 最后活动线程与消息历史应继续可恢复
|
||||
|
||||
### 3. 本地快照不再明文持久化
|
||||
|
||||
修复后:
|
||||
|
||||
- `SettingsSnapshot`
|
||||
- Assistant thread records
|
||||
- `assistant-state-backup.json`
|
||||
|
||||
都改为 sealed local state,而不是明文 JSON/SQLite。
|
||||
|
||||
## 核心修复点
|
||||
|
||||
- `SecureConfigStore` 的 secure storage 超时从 `400ms` 调整到 `5s`
|
||||
- secure storage 超时/异常时,secret 改为 durable fallback,而不是“只存内存”
|
||||
- 本地配置与任务线程统一做 AES-GCM sealed persistence
|
||||
- `assistant-state-backup.json` 升级为 schema v2,使用 `sealedState`
|
||||
- legacy plaintext prefs / local-state key fallback 增加迁移与清理
|
||||
- Assistant 线程持久化改为串行队列,避免异步晚到覆盖新状态
|
||||
|
||||
## 自动化验收
|
||||
|
||||
已执行结果:
|
||||
|
||||
- `flutter analyze`:通过
|
||||
- `flutter test`:未作为整套 baseline 通过,当前在 `test/features/ai_gateway_page_test.dart` 的 `Settings external agents detail shows Codex bridge runtime states` case 后挂住,未产生断言失败,但进程不退出
|
||||
- `flutter test test/runtime/secure_config_store_test.dart test/runtime/app_controller_execution_target_switch_test.dart test/runtime/app_controller_ai_gateway_chat_test.dart test/features/settings_ai_gateway_persistence_test.dart test/runtime/app_controller_gateway_token_state_test.dart`:通过
|
||||
- `flutter test integration_test/desktop_navigation_flow_test.dart -d macos`:通过
|
||||
- `flutter test integration_test/desktop_settings_flow_test.dart -d macos`:通过
|
||||
- `flutter build macos --release`:通过
|
||||
- `flutter build ios --simulator`:通过
|
||||
- `make install-mac`:通过
|
||||
|
||||
补充说明:
|
||||
|
||||
- 两个 macOS integration 都出现 `Failed to foreground app; open returned 1`,但设备跑断言本身通过,输出包含 `All tests passed!`
|
||||
- 当前未把挂住的 `ai_gateway_page_test` 假定为通过;它被保留为现有测试阻塞项
|
||||
|
||||
## 当前机器实机复测
|
||||
|
||||
已在当前机器完成两轮宿主级复测。
|
||||
|
||||
第一轮,重启恢复:
|
||||
|
||||
1. 配置本地 Gateway
|
||||
2. 退出 App
|
||||
3. 重新打开确认配置和任务会话仍在
|
||||
|
||||
第二轮,覆盖安装恢复:
|
||||
|
||||
1. 再次执行 `make install-mac`
|
||||
2. 重新打开 `/Applications/XWorkmate.app`
|
||||
3. 复查本地状态持久化产物
|
||||
|
||||
结果:
|
||||
|
||||
- `/Applications/XWorkmate.app` 可正常重新打开
|
||||
- 本地 SQLite 状态仍为 sealed payload,没有回退成明文
|
||||
- `assistant-state-backup.json` 仍为 `schemaVersion = 2` 且包含 `sealedState`
|
||||
- legacy `SharedPreferences` 中的 `flutter.xworkmate.settings.snapshot` 在新版 App 启动后一轮迁移后被清理
|
||||
- `gateway-auth/` 目录下未再残留 `local-state-key.txt`
|
||||
- 第二次覆盖安装后,上述状态保持不变
|
||||
|
||||
## 宿主级检查
|
||||
|
||||
需要确认:
|
||||
|
||||
- `config-store.sqlite3` 中的本地状态是 sealed payload,而不是明文 JSON
|
||||
- `assistant-state-backup.json` 为 schema v2 且包含 `sealedState`
|
||||
- `settings-snapshot.json` / `assistant-threads.json` 如果存在,内容也应为 sealed payload
|
||||
- 不出现明文 token / password
|
||||
- 旧版 `local-state-key.txt` 若存在,应完成一次迁移并被清理
|
||||
|
||||
当前机器检查结果:
|
||||
|
||||
- `config-store.sqlite3`:通过
|
||||
- `assistant-state-backup.json`:通过
|
||||
- `settings-snapshot.json` / `assistant-threads.json`:存在且为 sealed payload
|
||||
- 明文 `token/password`:未发现
|
||||
- `local-state-key.txt`:未发现,说明旧文件已迁移并清理
|
||||
|
||||
## 兼容与边界
|
||||
|
||||
- `.env` 仍然只是 Settings -> Integrations -> Gateway 的预填来源,不会变成持久化真值源
|
||||
- 用户发起连接时,仍然使用当前表单值做即时握手,不依赖 secure-store 回读
|
||||
- UI 布局不变,只修改持久化和恢复逻辑
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Secure Local Persistence Architecture](/Users/shenlan/workspaces/cloud-neutral-toolkit/XWorkmate.svc.plus/docs/architecture/secure-local-persistence-architecture.md)
|
||||
- [Secure Local Persistence Postmortem](/Users/shenlan/workspaces/cloud-neutral-toolkit/XWorkmate.svc.plus/docs/cases/secure-local-persistence-postmortem.md)
|
||||
83
docs/releases/2026-03-22-secure-persistence-social-copy.md
Normal file
83
docs/releases/2026-03-22-secure-persistence-social-copy.md
Normal file
@ -0,0 +1,83 @@
|
||||
# 2026-03-22 Secure Persistence Social Copy
|
||||
|
||||
## X
|
||||
|
||||
```text
|
||||
XWorkmate 刚发了一个很关键的稳定性补丁:
|
||||
|
||||
修复了 macOS App 在重启 / 覆盖安装后,本地 Gateway 配置、已保存凭证和任务会话可能丢失的问题。
|
||||
|
||||
这次没有改 UI,重点是把本地 settings、assistant threads 和 recovery backup 全部切到 secure-storage 前提下的 sealed persistence。
|
||||
|
||||
结果很直接:
|
||||
- 重启后状态不再丢
|
||||
- 覆盖安装后状态继续保留
|
||||
- 本地 snapshot / backup 不再明文落盘
|
||||
|
||||
#Flutter #macOS #AIGateway #SecurityEngineering
|
||||
```
|
||||
|
||||
## 领英
|
||||
|
||||
```text
|
||||
我们刚完成了 XWorkmate 一次很典型、也很值得发出来的桌面应用可靠性修复。
|
||||
|
||||
问题表面上看是“App 重启后本地配置丢失”,但根因并不只是一个保存 bug。我们最终定位到几层叠加问题:
|
||||
|
||||
1. Secure storage 读写被硬性套了 400ms 超时,超时后直接退化成“只存内存”
|
||||
2. 本地 settings / task session 的恢复链路里还残留 plaintext migration 路径
|
||||
3. Assistant thread 的异步持久化存在晚到覆盖新状态的竞态
|
||||
|
||||
这次修复后,我们把本地持久层重构为:
|
||||
|
||||
- FlutterSecureStorage 作为 secret 和 local-state key 的主信任根
|
||||
- SettingsSnapshot、assistant thread records、recovery backup 统一做 AES-GCM sealed persistence
|
||||
- SQLite 不可用时,仍通过 sealed durable files 保证可恢复
|
||||
- secure storage 失败时,长期 secret 进入 durable fallback,而不是消失在会话内存里
|
||||
|
||||
对用户来说,变化是简单的:
|
||||
|
||||
- 重启后,Gateway 配置和任务会话不再丢
|
||||
- 覆盖安装后,本地状态继续保留
|
||||
- 本地 snapshot / backup 不再明文落盘
|
||||
|
||||
这类修复的价值,不在于“加了加密”四个字,而在于把“当前请求可用”“已经持久化”“已经安全持久化”这三件事重新分层,并让产品行为和用户预期重新对齐。
|
||||
|
||||
#SoftwareArchitecture #SecurityEngineering #Flutter #DesktopApp #Reliability
|
||||
```
|
||||
|
||||
## 小红书
|
||||
|
||||
```text
|
||||
最近把 XWorkmate 修了一个很真实的坑,值得单独记一笔。
|
||||
|
||||
用户反馈是:
|
||||
“这次明明连上了,为什么重启以后本地配置又没了?”
|
||||
|
||||
一开始看像保存没写进去,继续往下查才发现问题更深:
|
||||
|
||||
1. secure storage 只要慢一点,旧逻辑 400ms 就判失败
|
||||
2. 失败后不是写持久化兜底,而是直接退成“只存内存”
|
||||
3. 所以当前会话看起来正常,App 一退出,token / password 就跟着没了
|
||||
4. 更糟的是,本地 settings 和任务会话的旧迁移链路里还残留明文落盘
|
||||
|
||||
这次补丁做了几件事:
|
||||
|
||||
- secure storage 超时从 400ms 提到 5s
|
||||
- secret 异常时走 durable fallback,不再只活在内存里
|
||||
- 本地 settings、assistant threads、backup 全部改成 sealed persistence
|
||||
- 修掉旧版 plaintext migration
|
||||
- 补上 assistant thread 持久化竞态保护
|
||||
|
||||
结果就是:
|
||||
|
||||
- 重启后本地 Gateway 配置不再丢
|
||||
- 覆盖安装后任务会话还能回来
|
||||
- 本地 snapshot / backup 不再是明文
|
||||
|
||||
这类问题最难的点,不是修一行代码,而是把“能连上”和“真的保存了”分开看。
|
||||
|
||||
桌面 App 做到最后,用户要的不是一个当下能跑的 demo,而是一个重启以后还记得自己的工具。
|
||||
|
||||
#独立开发 #Flutter #桌面应用 #AI工具 #产品修复复盘
|
||||
```
|
||||
Loading…
Reference in New Issue
Block a user