fix: avoid history reclamation during structure-only upgrades

Ordinary database schema upgrades previously ran VACUUM on free pages and
showed misleading history-cleanup progress. Separate structure upgrades from
required legacy conversions, retaining reclamation intent only for retries
within the same startup. Preserve released data without new schema versions
or persistent migration markers; Agent and normal writes remain unchanged.

Add real SQLite and Worker regressions, bilingual operation-specific startup
copy, and migration documentation. Synchronize native Electron drag input
with actual resize state without weakening layout assertions.

Validation: 47 focused migration tests passed; typecheck, lint and diff checks
passed. Final full suite: 5137 passed, 74 skipped, 2 unrelated Electron failures.
Both failing Electron tests passed an isolated rerun; full CI remains unverified.

Release note: 修复普通数据库结构升级再次整理历史和回收空间的问题;仅在需要转换旧数据时执行相关处理,启动页准确显示当前升级操作。
This commit is contained in:
mesalogo
2026-09-26 12:42:08 +08:00
parent e6ac0734de
commit 4c7193e1ea
21 changed files with 441 additions and 61 deletions
+9 -1
View File
@@ -18,6 +18,11 @@
- 先修新增数据路径,再修历史数据。历史转换与新写入尽量使用同一套转换函数。
- 普通 schema 变更使用既有 `PRAGMA user_version` 和 SQLite 事务;
已升级 schema 不允许旧客户端强行打开,不通过回写版本号支持降级。
- 将结构升级、旧内容转换和空间回收分别判断。结构版本落后不代表需要整理历史;
普通更新产生的空闲页也不是旧数据证据。只有明确的旧内容转换才允许检查空闲页并
执行 `VACUUM`,后续新增结构迁移不自动继承全库回收。启动页按实际操作显示状态。
- 同一次启动的重试可在内存中保留已确认的转换/回收决定;退出后重新根据数据库
schema 和未转换内容判断,不为仅剩空闲页增加持久化标记或重复启动回收。
- 大数据转换不得同步阻塞 Electron Main 或 Renderer。放到工作线程,在业务连接
打开前处理,并使用应用的共享字体、主题与页面组件显示进度和可执行的失败提示。
- 根据实测设置有界读取批次与缓存,批次提交后再报告进度。保持 SQLite 事务与
@@ -40,6 +45,9 @@
对象比较必须覆盖真实生产形状和 JSON 往返,尤其是可选字段值为 `undefined` 时与字段缺省
的差异。再次修复已发布迁移产生的数据时,提高目标 schema,并覆盖已升级用户的入口;
不能只处理尚未迁移的旧库。
仅修改升级调度、不改变存储格式时不增加 schema。回归必须覆盖已完成历史转换的库
在后续结构升级时仍有空闲页,确认不重新转换或 `VACUUM`,并验证真实 Worker、
同次启动重试和下一次启动的区别。
### 真实旧库
@@ -71,5 +79,5 @@
表示,并回收空间。具体规则见
[执行记录存储与升级回收](../features/assistant-workbar/execution-history-storage.md),
数据保留与真实旧库验证见[工作栏进度](../features/assistant-workbar/progress.md)。
- 当前未发布源码:schema 35 同时整理旧快照和 schema 34 的重复工具块,工具字段按
- Desktop 0.13.2:schema 35 同时整理旧快照和 schema 34 的重复工具块,工具字段按
JSON 可保存的值比较,不因 `undefined` 被省略而误判变化。
@@ -13,6 +13,8 @@
处理速度;处理时间取决于历史数据量与磁盘速度,不承诺固定几分钟。用户可以退出,
下次启动重新检查并继续转换未完成的数据;完成后回收空闲页并进入应用。失败时留在
该页提供重试与退出,磁盘空间不足会明确说明,不删除聊天来腾出空间。
以上转换和回收只针对仍需转换的旧格式数据;普通结构升级使用独立状态,不宣称正在
清理子任务历史,也不会仅因数据库有空闲页而执行全库回收。
## 存储规则
@@ -94,7 +96,10 @@ Renderer 合并时独立接收 Main 的置顶属性,即使本地正文更新
启动检查只根据旧 schema 或仍未转换的笔记正文决定是否进入升级流程;正常写入、更新
与删除产生的 SQLite 空闲页不是旧格式证据。迁移后新增数据使用当前格式,不因正常使用
再次迁移。同一次启动已进入的迁移流程可重试压缩;若正文已转换、仅压缩中断,退出后
再次迁移。进入升级流程后仍分别判断结构、笔记转换与空间回收,不把旧 schema 当作
回收许可。回收只用于实际存在旧子任务事件(schema < 35)、旧活动记录(schema < 44)
或旧笔记正文的转换;例如 schema 45/46 升到当前结构不会自动转换历史或 `VACUUM`。
同一次启动在内存中保留这一决定,只有原本需要回收的流程可重试压缩;若正文已转换、仅压缩中断,退出后
重开不单凭空闲页重新进入迁移,剩余页由 SQLite 后续写入复用。不新增持久化迁移标记。
桌面在打开业务数据库之前启动独立 Node 工作线程,使用同一套编码和还原函数转换旧库。
@@ -108,8 +113,8 @@ Renderer 合并时独立接收 Main 的置顶属性,即使本地正文更新
在转换前复制整份膨胀内容。回收仍需要额外可用磁盘空间,失败保留已提交转换并允许重试。
子任务整理对应 schema 35;schema 34 用户也会执行该整理,成功后后续启动直接跳过。
继续复用 schema 34 的子任务事件部分索引和同一差量表示。当前业务初始化随后在事务中
升级为 schema 36,新增 `pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0, 1))`,
继续复用 schema 34 的子任务事件部分索引和同一差量表示。业务初始化随后在事务中
执行后续结构迁移,其中 schema 36 新增 `pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0, 1))`,
已有会话默认未置顶,保留原消息与时间戳;失败回滚本次字段迁移。
低于该 schema 的旧版客户端不能重新打开已经升级的数据库。需要回退客户端时,必须使用
升级前备份,不要修改 `user_version` 强行绕过版本检查。
@@ -120,6 +125,8 @@ Renderer 合并时独立接收 Main 的置顶属性,即使本地正文更新
- 工具 fixture 必须含生产对象会携带的 `undefined` 可选字段,并经过实际 JSON 写入/重读。
同时验证旧完整事件和 schema 34 差量事件的整理、混合重试及无变化记录不被重写。
- 验证远程重复事件、冲突事件,以及消息更新失败时事件和过程缓存的事务回滚。
- 覆盖已完成历史转换、但正常聊天更新产生空闲页的后续结构升级:无转换进度、无
`VACUUM`,会话、笔记、任务事件与完整性不变;验证真实 Worker 和同次启动重试。
- 在真实旧库的已核验备份副本上运行 portable 的正常启动入口,核对事件逐条等价、
其他用户表内容、数据库完整性和实际文件大小,不能只统计空闲页或只测试内存 fixture。
- 同时验证真实本地 Runtime 和共享 Linux Host 的桌面持久化路径。真实场景必须让主代理
@@ -1,5 +1,30 @@
# 工作栏实现与验证进度
## 2026-09-26 结构升级与历史回收分离(未发布)
- schema 45/46 的回归先复现普通结构升级执行 `VACUUM`,修复后不转换历史、
不回收正常空闲页,保留聊天、笔记、事件及 SQLite 完整性。
- 启动决定在内存中分别保留笔记转换与回收需求,Worker 重试复用原决定;
不增加 schema 或持久标记,不修改正常写入。规则见
[旧库升级](./execution-history-storage.md#旧库升级)。
- 真实 Node Worker 验证结构升级、旧笔记转换、取消与重试、文件大小和内容保留;
启动调度回归验证允许/禁止回收的两种重试,中英文页面区分实际操作。
六个定向测试文件 **47 项通过**,类型检查、lint 和 `git diff --check` 通过。
- 完整 `npm test`:**5,138 通过、74 跳过、1 失败**。未修改的
`tests/magic-notes-layout.electron.test.ts` 原生拖拽断言失败:首次索引列保持 168,
单独复跑则 AI 列保持 280,均未达到拖动后的预期宽度。该 fixture 直接加载
Magic Notes 和模拟 API,不运行本次存储升级路径。随后修正测试的原生输入同步:
等待焦点、拖拽建立、宽度更新及结束,并明确左键按下状态,未弱化断言或改变产品
布局;独立复跑及随后完整测试中的该用例通过。
- 最终完整重跑:**5,137 通过、74 跳过、2 失败**,迁移相关及布局拖拽均通过。
`inline-help.electron.test.ts` 超时,`overlay-layering.electron.test.ts` 子进程退出码
为 null;这两个未修改的用例随后一起独立复跑 **2 项通过**,未提高超时或修改断言。
最终 typecheck、lint、`git diff --check` 均通过。独立复跑不替代完整测试结果,
不宣称本次全量或 CI 全绿。
- 本机与托管 SSH 历史共用 Desktop 数据库升级路径;Agent、Runtime、模型桥和
远程协议未变,不需要部署 Host 补丁。本次未调用模型,未改写用户数据库,
尚无本次修复的 CI 或发布验证。
## 2026-09-21 重复历史迁移补修(Desktop 0.13.9 候选)
- 0.13.8 的检查仍在已有笔记时把 `freelist_count > 0` 当作迁移依据;正常聊天更新
+7 -2
View File
@@ -467,8 +467,13 @@ Electron cases with substituted model output; live-provider checks remain opt-in
migration and backup requirements are defined in the
[storage contract](./technical-design.md#file-storage-and-writes).
- The startup worker converts one entry per transaction and verifies hydrated
readback before clearing the legacy payload. Reclamation checks the freelist
on every attempt, including retries after conversion has finished. Committed
readback before clearing the legacy payload. The original implementation checked
the freelist on every attempt. This was narrowed by the September 21 startup
fix and the September 26 structure-upgrade fix: only a required legacy
conversion enables reclamation, with its decision retained for same-startup
retries, not later launches. Current rules are in the
[migration contract](./technical-design.md#schema-38-migration).
Committed
saves/deletes/resets remain successful when manifest or GC cleanup fails;
failures are logged and cleanup is retried on later access or reopen.
- Storage implementation handoff reports 135/135 focused tests passed across
+10 -6
View File
@@ -175,12 +175,16 @@ Already converted entries are reconciled on retry. Matching body revisions do
not require rehydrating binary payloads. Direct `AssistantDatabase.initialize`
callers and tests also run storage repair.
Space reclamation is separate from conversion. Every worker attempt checks
`PRAGMA freelist_count` and runs checkpoint/VACUUM when free pages remain.
Cancellation after the last converted entry or a VACUUM failure therefore
allows reclamation to retry on the next startup, even with no legacy rows left.
The schema marker alone does not establish that conversion and reclamation
finished. General migration requirements are in the
Space reclamation is separate from structure upgrades and conversion. Startup
detects legacy note payloads and retains the conversion/reclamation decision in
memory for that startup's worker retries. Only a required legacy conversion
permits checking `PRAGMA freelist_count` and running VACUUM. A structure-only
upgrade does not repair notes in the worker or reclaim ordinary free pages.
Cancellation after conversion or a VACUUM failure can retry reclamation within
the same startup. After quitting, startup checks the schema and remaining legacy
payloads again; free pages alone do not trigger another upgrade, and SQLite can
reuse them. No additional persistent migration marker is stored.
General migration requirements are in the
[database migration guide](../../development/database-migrations.md).
## Backup and Restore
+23
View File
@@ -7,6 +7,25 @@
修正复发缺陷时,优先建立一条能覆盖整类问题的机制(共享选择器、共享函数、能发现全部实例
的测试),而不是逐个点名修补。逐个修补正是这些条目反复出现的原因。
## 普通结构升级再次触发历史回收
**症状**:历史已经转换完成,后续升级仍显示清理历史并执行耗时的全库回收。
**原因**:只修复启动检测不够;普通旧 schema 仍会进入升级 Worker,若共享升级方法
把任意空闲页当作回收依据,就会在以后每次结构升级时重现。
**规则**:共享启动决定分别描述内容转换与回收,普通结构升级默认不回收。只有具体
旧内容转换允许回收,同次启动重试沿用内存决定;下一次启动重新检测,不增加持久标记。
具体规则见[迁移指南](../development/database-migrations.md)。
**防复发**:schema 45/46 的真实 SQLite fixture 保留正常写入产生的空闲页,断言
无历史转换、无 `VACUUM`、内容与完整性保留;真实 Worker 验证取消及重试,
启动调度测试分别覆盖允许和禁止回收的决定在重试中不变。
**历史**:`8dce1d7` 和 `38d0923` 先后修正启动检测,后者随 0.13.9 发布;
2026-09-26 检查 0.13.15 的 schema 46→47 路径发现共享 Worker 仍无条件按空闲页回收。
本次修复在共享操作层分离结构升级与回收,而非给 46→47 增加特例。
## 异步界面测试只等待部分状态
**症状**:本地通过的界面测试在完整 CI 中找不到预期控件,或在前一步更新后立即读取到旧状态。
@@ -15,11 +34,15 @@
按请求完成和最终可见状态同步,不以另一个元素出现或消失代替完整操作结束。
测试请求重试时显式控制后一次响应,分别断言等待中和响应完成后的状态;
同一异步操作的多个最终断言放入同一个 `waitFor`。不要只延长全局超时或放宽断言。
Electron 原生拖拽测试应确认窗口焦点、拖拽状态建立、宽度更新及拖拽结束后再断言;
移动事件明确携带左键按下状态,不用固定睡眠代替输入处理完成。
**历史**:2026-09-26 检查 Desktop 0.13.14 标签 CI `36165805814` 与
main CI `36164698647`:原生能力重试用例在启动状态未稳定时打开选项,
浏览器关闭用例只等待页签消失便同步断言错误已清除。两处测试改用上述已有同步机制;
是否消除 CI 失败以修复提交的 Linux CI 结果为准。
同日 Windows 全量测试及独立复跑中,Magic Notes 原生拖拽分别停在索引列 168 和
AI 列 280。测试同步原生输入与实际状态后独立复跑通过,保留原有宽度和持久化断言。
## 整窗遮罩与原生拖动区域