# 2026-10-23 商品缓存重复报错的实测与修复 后续诊断补记:同日另一个诊断进程再次见到坏页,但 console / WinUI / 读写模式在 MSIX 重定向下仍存在不同物理视图。`artifacts/database-recurrence-live-01.json` 的实际句柄指向 Codex LocalCache 副本,因此它不能证明正常用户客户端使用的库再次损坏,也不能据一次恢复宣称最初写坏原因已经解决。本轮没有据该诊断重置用户数据;持续健康与原始损坏根因仍待进一步确定。 ## 复现结论 实际客户端使用的商品库存在 B-tree 损坏:`quotes` 的树 5、第 341325 页出现 rowid 顺序错误。`MarketRepository.Search`、`GetStations`、`Statistics` 均能复现 SQLite Error 11。未取得证据确定最初写坏页面的具体操作或时刻。 此前的恢复没有覆盖实际客户端使用的数据库。相同 LocalAppData 字符串在 Codex/Windows Store 运行时存在重定向视图;此前恢复记录命中了 `Packages/OpenAI.Codex_2p2nqsd0c76g0/LocalCache/Local/...` 的副本。外部 Python 的 `ok` 结果不能代表桌面客户端使用的库。此次用桌面 apphost、客户端相同的 Microsoft.Data.Sqlite 与原生 SQLite 3.53.3,以及实际 WinUI 进程反复执行原查询,才复现并修复了实际使用的库。 证据保留在 `artifacts/database-recovery-20261023-02/`: - `desktop-before-repair.json`、`access-modes.json`:实际查询与错误栈。 - `desktop-original.db`:此次恢复前通过 SQLite backup API 取得的一致备份,包含 WAL 的已提交内容。 - `desktop-recovery-simulation.json`:用实际损坏库副本演练新恢复代码并持续读写。 - `install-client-backup.json`:通过相同 SQLite 实现安装经验证的恢复来源。 - `desktop-after-repair.json`、`live-after-ingestion.json`:恢复后重新打开、查询及继续接收行情后的校验。 ## 数据与程序变更 恢复来源由官方 `.recover` 输出按内嵌 JSON、站点、商品、价格、数量、来源和快照时间核对后重建,包含 15,110 个站点、2,266,775 条报价、15,290 个快照。原损坏快照有 15,311 个站点,不能宣称完整无损恢复所有旧缓存;原始库仍保留。 此次由 WinUI 客户端自己的 `MarketRepository.Initialize` 完成实际数据库的恢复,记录于 `markets.db.diagnostics.jsonl`,进程 82176 的 `startup-corrupt` 和 `restored` 记录连续对应。恢复先保留损坏库,再由 SQLite backup API 事务写入,通过完整性检查、条数核对、重新打开检查与 WAL checkpoint;未直接覆盖正在打开的数据库文件,也未删除 WAL/SHM。 程序现在会在启动时检查商品缓存,保留有效备份,运行期间每 30 分钟在有新行情写入时更新备份。发现损坏时验证备份后恢复,并对失败写入重试一次。空缓存和无效备份不能替换有效备份。没有可用恢复来源时保留损坏库与错误记录,行情明确不可用,Journal 和导航仍可初始化。EDDN 数据库错误进入重试,不再使接收线程永久故障。成功读取后会清除对应缓存错误状态。 没有重置 `settings.json`、游戏事件历史或玩家数据。测试与恢复不进行驾驶、交易、游戏退出或重启。 ## 验收 - 5 项缓存恢复回归测试全部通过:真实页损坏、启动恢复、失败写入恢复后重试、无效/空备份保护、多连接并发与重开。 - 全量测试 502 项:496 通过、0 失败、6 项显式/游戏相关测试跳过,详见 `artifacts/market-cache-full-tests-final/`。 - 实际损坏库的隔离副本恢复后,完成 1,855 次写入、94 轮查询,随后保存、完整检查和重开,无错误。压力输入只写隔离副本,不进入用户行情库。 - 最终 WinUI 构建两次独立打开实际库,每次查询两轮六种排序、站点商品过滤与有坐标站点的区域数据,实际绑定 150 行,`HasError=false`;`work/visual/market-recovery/reopen-1/report.json` 与 `reopen-2/report.json` 均无失败,第二次无需再次恢复。 - 经用户明确确认后,正常关闭旧工具箱并更新 `artifacts/latest-client`,`Start-Toolbox.cmd` 优先启动该版本。部署 Core SHA256 为 `42559C6B9FA48C702141429B0FBB43228FC028CC25609AAEEF51822CA337CE88`。 - 启动脚本拉起正常客户端 PID 119512;60 秒四次采样共收到 922 条 EDDN 消息,接受 35 批行情,四次错误文本均为空,导航为 Idle。继续接收期间另行完整验证并更新健康备份。 - 后台查看最终商品页原图、状态区与表格放大图、正常客户端窗口;缓存错误条消失,真实报价可见。正常窗口 PrintWindow 取图未改变前台窗口。 首轮探针曾因选中的第一行站点缺少坐标,额外的区域规划检查失败;保留了 `client-first-open.json`,没有把它当作完整通过。最终探针选用缓存中实际具有坐标的站点完成区域查询。界面探针不启动 Journal 或绑定游戏通道。 本次验收范围为缓存错误与恢复机制,不替代此前暂停的实飞和跑商验收。 ## 2026-10-25:截断文件的恢复与全量重下 0.4.21 实际启动时另一次 SQLite Error 11 的库文件只有 1,226,625,024 字节,SQLite 页头记录的页数超过文件末尾。该文件连 schema 查询及作为 backup 目标都失败;原先“先备份坏库,再写回恢复来源”的流程因此无法完成,并产生空归档。不能据此确定造成最初截断的进程或操作。 恢复现在保留原库及其 WAL/SHM,验证可用备份后写入新的 `markets.db.recovered-*.db`,核对条数、完整性、checkpoint 和重新打开结果,再用同目录 `markets.db.active` 原子切换。现存 Repository 每次新建连接都会读取该选择记录;文件名只允许指向同目录的恢复库。截断、重开、旧对象跟随切换及越界文件名保护均有回归测试。另在打开备份目标前检查磁盘余量,空间不足时保留原验证备份。8 项 MarketRecovery 测试通过,日志在 `artifacts/tests/market-cache-spaceguard-20261025/`。 原始坏库、验证备份、物理文件句柄、SHA256 和恢复过程保留于 `artifacts/market-cache-20261025-1612/`。用于恢复启动的备份有 17,586 个站点、2,631,045 条报价,但报价已超过默认 6 小时时效,因此恢复启动本身不代表当前可规划。程序没有改写采集时间或放宽默认时效。 依用户“损坏就全部重新下载”的要求,随后停止额外 salvage,改从 Spansh 官方站点包 `https://downloads.spansh.co.uk/galaxy_stations.json.gz` 重建完整站点与行情。下载和导入独立于当前活动库,保留个人 Journal、settings 和原始损坏证据。官方源身份、断点下载进度、完整文件 SHA256、真实源数据对照、导入条数与最终完整性报告记录在 `artifacts/market-redownload-20261025/`;下载或导入未完成的 generation 不会发布为活动缓存。