# SQLite 跨引擎迁移补充验收 ## 范围与任务状态 本记录补充 SQLite 与 MySQL、PostgreSQL 之间的四个应用级迁移方向。它不替代真实浏览器、外部邮件最终收件、凭据轮换、证书自动续期及全项目生产评审。 - [✓] 已核对修改前基线 `d4f1b0c` 与远端一致,用户原报告保持不变。 - [✓] 混合引擎验收工具与反例测试已补齐。 - [✓] SQLite → MySQL、SQLite → PostgreSQL、MySQL → SQLite 三个方向实机通过。 - [!] PostgreSQL → SQLite 两次均在初始化恢复码生成请求上超时,尚未进入迁移,不能放行。 - [✓] 独立复核文件摘要、账号权限、测试单元及正式服务保护状态。 - [✓] 最终 Linux 部署回归 149 项通过、零跳过;本地运行时回归 79 项通过。 **结论:本轮只关闭三个迁移方向,不能宣称六个跨引擎方向全部通过,更不能宣称全项目达到生产部署级别。** ## 候选与隔离边界 本轮仅扩展验收工具,应用后端、前端和插件源码均未修改。使用上轮已编译候选 `smail-character`,版本 `review-character-20260921`,SHA256 为 `97ad202b67e57e6284d7707928cf4591a5d2d676344d7b67f003630242e28924`。启动前逐项校验 611 个后端 Go 源码摘要;不把复用候选描述为本轮全资产重新构建。 每轮使用新建空实例、随机 SQL 库和专用账号。测试域名、随机管理员口令及自签测试证书只服务于隔离协议探针,不构成域名、正式证书或管理员交互验收。实例通过独立网络、受限 systemd 单元及 192 MiB 应用内存上限运行,不停止正式 Smail、Nginx、MySQL、PostgreSQL、Scert、FDM,不清空正式库。 SQLite → SQL 仅保留已停用的 `smail.db` 作为离线源证据;SQL → SQLite 只允许活动文件 `migrated.db`。业务表与配置历史都使用同一目标数据库,不能生成第二个状态库。离线证据不是第二个活动数据库,也不代表正式部署必须保留旧库。 ## 工具与断言 - `test/tools/runtime_mixed_migration.py`:只接受一端 SQLite、一端 SQL;SQLite 文件名固定、路径受限、只读查询且缺库不能新建。 - 使用正式迁移预览、执行和系统重启 API,不直接改写配置模拟成功。待重启拒绝写入,内部重启继续使用原会话。 - 内部重启、真实系统重启、故障恢复后三次分别检查 SQLite 文件描述符和 SQL 连接;要求源停止使用、目标承担活动读写。 - 目标 SQL 账号锁定或 SQLite 文件权限收紧后,应用应明确启动失败;配置保持指向目标,不静默回退源库。异常路径也恢复账号或权限。 - 迁移前邮件与各阶段新邮件通过 SMTP、IMAP、POP3 检查中文正文和附件;新邮件只进入目标库。 - 配置历史比较四列原始字节并校验载荷摘要;源库逻辑指纹在后续写入、重启和故障注入期间保持一致。 - 扫描实例中 SQLite 扩展名、文件头、侧文件和符号链接,拒绝隐藏库、改名库、嵌套额外库。 ## 当前实测记录 下表实例目录均位于 `/var/tmp/smail-review-runtime-` 前缀下。 | 方向 | 实例后缀 | 内部重启 PID | 系统重启 PID | 故障恢复 PID | 结果 | | --- | --- | --- | --- | --- | --- | | SQLite → MySQL | `20260921-074846` | `790031`,不变 | `790465` | `790531` | 通过 | | SQLite → PostgreSQL | `20260921-075415` | `790691`,不变 | `790937` | `791027` | 通过 | | MySQL → SQLite | `20260921-075919` | `791137`,不变 | `791541` | `791650` | 通过 | | PostgreSQL → SQLite | `20260921-080502` | 未执行 | 未执行 | 未执行 | 初始化请求超时 | | PostgreSQL → SQLite,原条件复测 | `20260921-080854` | 未执行 | 未执行 | 未执行 | 相同初始化请求超时 | 两个 SQLite → SQL 方向在内部重启、系统重启、故障恢复后的 SQL 连接数均为 `3 / 2 / 2`,旧 SQLite 均未被进程打开。MySQL → SQLite 三阶段源 SQL 连接数均为零,活动 SQLite 均已打开,运行目录没有 `smail.db` 或第二个状态库。 三个成功方向均保留同一条源配置历史的原始字节与摘要;待重启写入拒绝、旧会话恢复、旧邮件和各阶段新邮件的正文及附件均通过。目标不可用时 PID 为零、服务状态为 failed、退出码为 1,目标配置保留且源库逻辑指纹不变。恢复后读写正常。五轮均无 OOM、资源采集错误或清理错误。 独立复核覆盖三个成功实例及两个失败实例,共 47 份云端/本地证据摘要、七个运行工具和 611 个后端源码摘要。五个 SQL 专用账号均锁定;三个 PostgreSQL 账号均不能登录、不能建库、不是超级用户。测试应用、探针、桥接和网络锚点全部停止;正式六服务的 PID、InvocationID、重启次数及正式程序摘要均未改变。 复核索引为 `mixed-recheck-20260921-081310.json`,同时保留原始首轮与复测汇总,不以仅包含成功样本的新文件替代失败记录。 ## 回归与保留边界 新增十三项反例测试,覆盖路径、缺库不创建、只读连接、历史损坏、隐藏数据库、源连接残留、单活动文件以及故障恢复。初次本地测试发现测试夹具未关闭 SQLite 连接,Windows 临时目录清理失败;修复夹具显式关闭后通过,不将其描述为产品缺陷。 本地运行时最终回归 79 项通过,日志 `mixed-local-runtime-final.log`。第一次 Linux 全部署回归 148 项通过、零跳过,证据 `python-linux-result-20260921-074736.json`;随后增强隐藏库反例的相互隔离并增加单活动文件反例,最终 Linux 回归实际执行 149 项、零跳过、退出零,证据 `python-linux-result-20260921-081152.json`。最终套件运行约 28.64 秒、内存峰值约 46.3 MiB、交换零;这不是应用容量验收。 ### 保留的 PostgreSQL 首次安装超时 实例 `/var/tmp/smail-review-runtime-20260921-080502/` 在初始恢复码生成请求上触发 30 秒 HTTP 读取超时,尚未开始迁移,不能算作 PostgreSQL → SQLite 的迁移成功或迁移失败反例。该轮应用 CPU 配额为 25%,应用峰值约 105.3 MiB、交换零、OOM 零,约 44.55 秒内消耗 10.296 秒 CPU。CPU 限额可能影响耗时,但这些指标不足以独立确认超时根因。 该轮正式服务未改变,测试进程、桥接和网络锚点已停止,PostgreSQL 专用账号已锁定。失败证据保留在 `mixed-runtime-evidence/smail-review-runtime-20260921-080502/`,原始汇总为 `mixed-results-20260921-074833.json`。 第二轮 `080854` 保持同一候选、资源限制和超时断言,新建空库与账号,仍在同一请求发生读取超时。结果为 `mixed-results-20260921-080840.json`,对应原始证据目录同样保留。收尾完整性检查因目标尚未创建而报告断言失败,不能误述成目标 SQLite 已损坏。没有继续盲目重试,也没有放宽限制或将失败从汇总中移除。 源码 `backend/services/credentials/service.go` 的该路径逐个生成十枚恢复码,并串行调用 `password.Hash` 后写入事务。后续需要分别采集密码计算、数据库事务和请求截止时间的证据,才能确定超时原因与整改方案;不能只凭当前日志将其归为 PostgreSQL 迁移缺陷,也不能降低密码哈希强度来获取通过结果。 本地证据根目录为 `test-log/production-remediation-20260921/`。成功与失败证据均保留,不删除源库、失败目标或用户文件。