# 数据库迁移重启补充验收 ## 结论与范围 北京时间 2026 年 9 月 21 日,使用隔离云端实例补充 SQLite 迁移、重启及目标库故障恢复验收,并修复真实发现的 HTTP/HTTPS 重启确认响应丢失问题。本轮未替换 `/opt/smail` 的正式程序,未清空正式数据库,未停止其他项目或共享数据库服务。 - [✓] SQLite 迁移失败不覆盖目标,失败后解除写维护状态。 - [✓] SQLite 迁移后内部重启、系统级进程重启及目标不可读后的恢复。 - [✓] HTTP/HTTPS 活动响应排空修复与本地、Linux 云端专项复核。 - [✓] 最新候选 SQLite/MySQL/PostgreSQL 空数据基础回归,配置历史与业务同库。 - [✓] MySQL/PostgreSQL 同引擎切库后的真实进程重启和故障恢复,见后续补充记录。 - [✓] MySQL ↔ PostgreSQL 跨引擎应用级迁移、两类重启及账号故障恢复,见 `SQL跨引擎迁移补充验收.md`。 - [ ] 涉及 SQLite 的四个跨引擎应用级迁移与重启方向。 - [ ] 迁移复制、提交途中进程中断后的恢复。 - [!] 浏览器、外部收件、自动续期、安全告警、凭据轮换及其他生产门禁仍未关闭。 上述通过项不代表整个项目审计结束,也不是正式实例升级或生产放行。 ## 重启响应缺陷与修复 重启接口交付信号后,主循环立即停止服务。原 HTTP/HTTPS 停服直接调用 `http.Server.Close()`,可能在响应及会话中间件完成前关闭连接。云端收到 `RemoteDisconnected`,同时服务日志确认重启请求已经被处理;不能把这种失败按偶发网络问题忽略。 新增统一停服方法:先调用 `Shutdown` 等待在途响应,期限为 10 秒;超时才强制关闭并保留错误。两种 Web 监听都使用此方法,没有使用任意等待或放宽客户端验收标准。 | 检查 | 证据与结果 | | --- | --- | | 修复前确定性反例 | `run-2026-09-20T21-08-10-337Z-39880`;HTTP、HTTPS 均提前关闭,客户端 EOF | | 修复后五轮竞态回归 | `run-2026-09-20T21-12-58-775Z-4492`;通过 | | 五包回归 | `run-2026-09-20T21-13-36-986Z-3400`;HTTP、控制器、重启、数据库迁移、初始化包通过;不宣称可选集成全部零跳过 | | Linux 云端专项 | `smail-review-ssl-apex-20260921/20260921-051655-http-restart`;六个顶层测试通过、零跳过、退出零 | | 验收器单元测试 | `test_runtime*.py` 共 42 项通过 | `run-2026-09-20T21-09-33-980Z-37428` 保留首次超时测试夹具失败:请求体未消费且缺少独立释放路径,测试服务器清理等待活动请求。确认 PID 后仅停止本轮测试进程;修正夹具后另跑通过,不将夹具错误描述为应用漏洞。 日志名中的 `2026-09-20T21...Z` 为 UTC,对应北京时间 9 月 21 日。 ## SQLite 实机证据 最终实例:`/var/tmp/smail-review-runtime-20260921-051723/`。 本地归档:`test-log/production-remediation-20260921/migration-runtime-evidence/smail-review-runtime-20260921-051723/`。 1. 从空库初始化、写入配置历史,真实 SMTP 收信后由 IMAP/POP3 读取中文正文和附件。 2. 迁移预览后故意占用目标,执行返回 HTTP 409、`DATABASE_MIGRATION_TARGET_EXISTS`;记录为 `failed / TARGET_EXISTS`,目标内容未被覆盖。随后配置写入成功,证明维护锁已释放。 3. 成功迁移至 `migrated.db`,配置代次推进至 4;等待重启期间业务写入返回 HTTP 503、`DATABASE_RESTART_REQUIRED`。 4. API 内部重启返回 `accepted`,旧 Cookie 会话仍有效;PID 和 InvocationID 不变,这不是进程替换。 5. `systemctl restart` 后 PID 和 InvocationID 改变,文件描述符证实仅打开目标业务库,不打开源业务库。 6. 将本轮目标文件权限改为 `000` 后再次启动,进程退出码为 1、MainPID 为 0、状态为 failed;配置仍指向目标库,没有静默回退旧库。 7. 在收尾保障路径恢复目标权限为 `0600`,重新启动成功。内部重启、系统重启、故障恢复三阶段均验证登录、三种协议认证、旧邮件及新邮件正文和附件。 8. 新邮件仅写入目标库。源库完整逻辑摘要未变,源配置历史一条、目标一条,`kind/id/payload/digest` 完整一致。 进程证据:内部重启前后 `773001`,系统重启后 `773123`,故障恢复后 `773224`。 只有 `migrated.db` 是活动业务数据库;`smail.db` 和故意占用的 `occupied.db` 保留为离线证据,不是额外运行数据库,未经许可不删除。 保留两轮云端失败:`045727` 为验收器未先处理基础回归留下的待重启配置,被正确拒绝;`050341` 为真实重启响应中断,促成本次应用修复。失败记录均未删除或改成通过。 ## 最新候选的 SQL 基础回归 | 数据库 | 隔离实例时间后缀 | 结果 | | --- | --- | --- | | MySQL | `20260921-052137` | 退出零、基础回归通过、配置历史一条、SQLite 文件零个 | | PostgreSQL | `20260921-052322` | 退出零、基础回归通过、配置历史一条、SQLite 文件零个 | 本地证据位于 `restart-runtime-evidence/`,摘要清单为 `restart-evidence-index.json`,均在 `test-log/production-remediation-20260921/` 下。逐一复核四份下载文件摘要,账号已锁定、数据库桥接已停止,OOM、资源采集错误和清理错误均为零。此处仅为 SQL 基础回归,不包含 SQL 切库进程重启。 最后重新读取六个保护服务状态,PID、InvocationID 与回归前一致,无运行中的 Smail 验收单元。正式程序 SHA-256 仍为 `c14ef1c5ba1dc2096c312c8357039a7330c985cbeac90765aed01d8151e05e1f`。 ## 构建与可追溯性 版本 `review-restart-20260921`,构建源码标识 `f589f10f0556daeb9437673322d904d17e0a2df2-dirty`。`restart-source-manifest.json` 记录 610 个后端 Go 文件的摘要,`restart-build-manifest.json` 记录候选产物。 | 产物 | SHA-256 | | --- | --- | | `smail-restart` | `21ec0820f9b09eb1f8262d6d444bbc5d235d4cf64115d6f4b3378be2083533a0` | | `smail-admin-restart` | `73330cd069570b7bf0181db5497465faad3897eb9b564d3045774bb35bc1472f` | | `http-restart.test` | `d09687338b3f5ded2ff034587292974c975c16e2a09b0595435622125da517ee` | 本阶段重新编译后端,复用前轮未改动的前端资源和三个 Linux 插件,不声称又完成一次前端重建。拟用的 PowerShell 构建脚本被执行策略拒绝,没有更改策略或执行该脚本,随后使用原生 Go 构建命令;保留的脚本不是实际完整构建成功证据。 静态扫描仅复扫变更的 HTTP 包:九个文件、2,035 行、零告警、零加载错误、退出零,未增加抑制。之前全项目仍有 38 条告警,详见 `静态安全告警复核记录.md`,不能据此把全项目扫描标为通过。 ## 后续 SQL 迁移补充 北京时间 9 月 21 日 05:37 至 05:45 左右,另建 MySQL、PostgreSQL 隔离源库和目标账号,使用重新编译的候选程序完成同引擎切库、内部重启、系统级进程重启、目标账号不可用时拒绝启动及恢复后的真实邮件读写。三阶段均证明源库活动连接为零、目标库有应用连接,业务与配置历史同库,运行目录没有 SQLite 文件。 完整范围、候选摘要、进程标识、资源与清理结果见 `SQL切库与部署回归补充验收.md`。前文 `052137`、`052322` 两轮仍仅代表当时的 SQL 基础回归,不把后来新增的证据倒填为旧测试已经覆盖。复制/提交途中进程中断与跨引擎应用级矩阵仍待验收。 ## 后续 SQLite 复制中断补充 9 月 21 日 06:17 的新空隔离实例完成未提交复制事务中的 SIGKILL 中断及恢复,随后正常迁移和重启恢复也通过。详见 `SQLite复制中断恢复补充验收.md`,包含两个夹具失败记录、原始目标及 journal 快照摘要、20 份证据复核与 131 项部署回归。本项仅关闭 SQLite 未提交复制阶段,不关闭提交临界点、配置切换临界点、SQL 数据库中断或跨引擎矩阵。 ## 后续混合引擎矩阵补充 详见 `SQLite跨引擎迁移补充验收.md`。SQLite → MySQL、SQLite → PostgreSQL、MySQL → SQLite 三个方向通过正式迁移、两类重启、目标不可用及恢复后的读写,三阶段证明只有目标库活动,配置历史同库。 PostgreSQL → SQLite 两次均在空安装恢复码生成请求的 30 秒读取超时处终止,尚未进入迁移;失败原样保留,不能宣称矩阵完整。47 份成功/失败证据及全部测试账号、测试单元和正式服务保护状态已独立复核。最终部署回归 149 项、零跳过,但不关闭初始化超时、迁移中断临界点或其他生产门禁。