← 返回首页
Record Kernel 维护模式与迁移运行骨架
# Record Kernel 维护模式与隔离迁移运行时
文档版本:Batch 005R
日期:2026-08-23
状态:Preflight、Copy、独立 Verify 实现与 PostgreSQL 16 测试定义已完成;真实数据库演练、Cutover 和业务库执行尚未实施
## 1. 目标与硬边界
本架构在 Batch 004 第二次 Expand、持久化维护模式和迁移操作状态机之上,实现旧 24×3 普通记录表到 Record Kernel 的隔离 Preflight、Copy 与独立 Verify。它解决的是“如何安全判断、复制并证明旧链数据已经进入同一 Kernel 权威集合”,不负责启用业务 Cutover。
本批继续遵守以下边界:
- 不连接或修改用户业务数据库;
- 不在当前环境执行 0010、Copy、Verify、Cutover、DROP 或旧表回填;
- 不做长期双写;
- 不切换 MEM-001 Repository、PC 首页、Capture、Seed 或 Export;
- 不把静态检查、pg-mem 或内存对象冒充 PostgreSQL 锁、约束和事务证据;
- 迁移报告不包含 title、summary、payload、snapshot、feature input/output 或用户原文。
本批形成时功能状态为 1 项 `implemented_local` 与 191 项 `partial`;截至 2026-08-25,MEM-001 至 MEM-008 与 TAX-001 至 TAX-008 已凭专用纵向证据升级为 16/176;截至 2026-08-29,IDN-001 至 IDN-008 再凭专用纵向证据升级,当前为 24 项 `implemented_local` 与 168 项 `partial`。迁移机制本身没有参与这些升级。
## 2. 第二次 Expand 与维护基础
`apps/api/drizzle/0010_second_expand_maintenance_runtime.sql` 继续保持 Expand-only:
- `record_revision.module_code` 为可空过渡列,并建立模块白名单与组合外键;
- `memory_text_entry_v2` 通过 owner/module/record 一对一引用 `record_core`;
- `record_migration_operation` 保存阶段、状态和脱敏证据;
- `maintenance_state` 保存跨实例维护所有者和版本。
0010 不包含旧业务数据 `UPDATE`、`DELETE`、`DROP`、`TRUNCATE`、`COPY` 或 Cutover。本批没有修改该迁移,也没有把它应用到任何数据库。
普通写入口继续通过持久化状态和 shared advisory lock 失败关闭;维护激活继续在同一外层事务中写入 active 状态、等待 exclusive drain lock,并保存真实 PostgreSQL 排空证明。Capture 长流程继续使用 session shared lease。
## 3. 固定来源与目标白名单
`source-table-map.ts` 只从权威 Catalog 派生 24 个模块,每个模块固定三个来源表:
```text
<table_prefix>_record
<table_prefix>_record_revision
<table_prefix>_feature_execution
```
合计 72 张旧普通记录表。另只读旧 `memory_text_entry`,用于判断 MEM-001 类型化关系是否一致;本批不把它复制到 `memory_text_entry_v2`。
Copy 的唯一目标是:
```text
record_core
record_revision
feature_execution
```
动态 SQL 标识符只能来自编译期白名单并经过 SQL identifier 校验;owner、moduleCode、recordId、operationId 等运行值全部使用参数化查询。任意未知模块、重复映射、危险标识符或非 PostgreSQL runtime 均失败关闭。
## 4. Planning 与 Frozen Preflight
Preflight 被拆分为两个不同权限的动作。
### 4.1 Planning Preflight
Planning 在维护状态未激活时执行,只生成诊断报告:
```text
取得数据库操作 advisory lock
-> 取得固定业务表 read lock
-> 在 REPEATABLE READ 中读取全部来源与目标
-> 生成七类 reconciliation
-> 保存非权威 planning 计数和指纹
```
Planning 用于提前发现阻断,但不能授权 Copy。它不声称数据在后续维护窗口内仍保持不变。
### 4.2 Frozen Preflight
进入 maintenance 并取得真实 PostgreSQL drain proof 后,Frozen Preflight 在新的事务中重新读取数据库:
```text
复核 operation=running/maintenance
-> 复核 maintenance active 且 operationId 一致
-> 复核 postgresDrainProof=true
-> 取得来源与目标表 read lock
-> 重新读取全部数据
-> 重新执行 reconciliation
-> 冻结 schema/source/target/report 指纹和 bucket 计数
```
只有 Frozen 报告 `blockingCount=0`,状态机才允许进入 Copy。Copy 不接受 Planning 指纹,也不接受调用方自行构造的维护状态布尔值。
## 5. 七类确定性对账
`migration-reconciliation.ts` 固定使用七类结果:
| 分类 | 含义 | 是否允许 Copy |
|---|---|---|
| `legacy_only` | 旧链存在、Kernel 不存在 | 是,唯一 Copy 白名单 |
| `kernel_only` | Kernel 已存在、旧链不存在 | 保留,不覆盖 |
| `same_id_equal` | 相同 ID 且规范化内容等价 | 跳过 |
| `same_id_conflict` | 相同 ID 但内容不同 | 阻断 |
| `logical_duplicate` | 不同 ID 但业务投影重复 | 阻断 |
| `invalid_source` | 来源违反 ID、JSON、字段或领域合同 | 阻断 |
| `orphan_or_drift` | revision/execution/MEM 孤儿或 owner/module/head 漂移 | 阻断 |
对账同时验证:
- record、revision、execution 的规范 ULID 与跨模块全局 ID 碰撞;
- owner、moduleCode 和来源表模块一致;
- title 去除首尾空白后长度为 1~200 个 Unicode 字符;
- summary 不带首尾空白且不超过 1,000 个 Unicode 字符;
- JSON 为受限普通 JSON,不含访问器、自定义原型、稀疏数组、危险原型键或非有限数字;
- 主记录 revision 等于唯一最大 revision;
- 主记录完整当前状态与最大 revision snapshot 等价,而不只是 payload 等价;
- execution 的 featureId 属于对应模块,旧 `mode` 显式转换为目标 `implementationStatus`;
- 旧普通 feature execution 只映射为 `record-action`;目标中已存在的 MEM-001 `dedicated-command` 作为 `kernel_only` 保留;
- MEM-001 类型化事实与记录 payload/owner/recordId 一致。
父记录一旦无效,其 revision 和 execution 不会进入 Copy 白名单,避免“无效主记录未复制、子记录却被计划写入”的部分迁移。
## 6. Canonical JSON 与指纹链
`canonical-json.ts` 使用版本化确定性序列化:
- 对象键按字典序排序;
- 数组顺序保持不变;
- 数据库时间通过受控 ISO 8601 投影进入快照;
- 拒绝循环引用、访问器、自定义原型、Symbol、BigInt、function、undefined、`NaN`、`Infinity`、稀疏数组、数组附加属性与原型污染键;
- 限制深度、节点、数组长度、字段名和总字节数。
Frozen evidence 同时绑定:
```text
schemaFingerprint
sourceFingerprint
targetFingerprint
reportFingerprint
bucketCounts
operationId
canonicalVersion
```
因此即使行数和 bucket 计数保持不变,只要任意来源或目标内容变化,Copy 仍会拒绝执行。
## 7. Copy 事务
Copy 只在 `running/copy`、maintenance active、同 operationId 和 PostgreSQL drain proof 仍成立时运行:
```text
开启新的 REPEATABLE READ 事务
-> 设置 30 秒 lock_timeout
-> 取得数据库操作 advisory lock
-> 按固定字典序锁定 72 张来源表、旧 memory 表和三个目标表
-> 重新读取全部来源与目标
-> 重建 reconciliation
-> 精确匹配 Frozen 指纹
-> 只插入 legacy_only record/revision/execution
-> 再次读取 Copy 后状态
-> 验证 legacy_only 已归零且 kernel_only 未丢失
-> 保存最小 Copy evidence
-> 业务数据与 evidence 同一事务提交
```
目标表使用 `SHARE ROW EXCLUSIVE`,来源表使用 `SHARE`。这既允许当前 Copy 事务写目标表,也阻止绕过应用维护门禁的其他会话直接写入来源或目标表。锁顺序固定,锁超时和死锁分别映射为稳定错误码。
Copy 不使用 `ON CONFLICT DO NOTHING`,避免把相同 ID 冲突、operation 重复、约束漂移或数据库竞态伪装成成功。回滚失败时连接会被销毁,不会把未知事务状态的会话放回连接池。
### 7.1 响应丢失重放
若 Copy 已提交但调用方未收到响应,相同 operationId 只有在当前数据库仍与已保存的 post-copy schema/source/target/report 指纹完全一致时才能重放结果。任何直接数据变化、schema 变化、operation 变化或 evidence 缺失都会返回稳定冲突,而不是再次插入或复用过期结果。
## 8. 独立 Verify
Verify 不信任 Copy 返回的内存对象。它在新的 PostgreSQL 事务中:
```text
复核运行阶段与维护状态
-> 取得独立的 read table locks
-> 重新读取 72 张来源表、旧 memory 表和三个目标表
-> 独立规范化并重新执行七类对账
-> 验证 Frozen 与 Copy 的完整指纹链
-> 要求 legacy_only/conflict/duplicate/invalid/orphan 全部为 0
-> 保存最小 Verify evidence
```
Verify 可以对完全相同的已通过快照进行幂等重放;重放不会再次更新 operation version。Verify 不是 READ ONLY 事务,因为通过后仍需在同一受控事务写入 evidence,但业务数据只读。
## 9. 迁移证据与隐私
迁移台账只保存:
- operationId、phase、status、version;
- 模块、表类型和 bucket 非负整数计数;
- schema/source/target/report SHA-256;
- canonical 版本;
- PostgreSQL drain proof 与稳定失败码。
台账合同拒绝自由正文、敏感键、凭据形态、超深结构和超长字符串。PostgreSQL 原始错误、SQL 文本、连接配置和业务行不会进入 evidence;数据库异常只映射为稳定迁移错误码。
## 10. PostgreSQL 16 测试定义
`record-kernel-copy-verify.postgres.spec.ts` 已定义一次性 PostgreSQL 16 Testcontainers 场景:
- 执行 0000~0010;
- 通过正式运行服务进入 maintenance 并取得 drain proof;
- legacy_only Copy、kernel_only 保留、独立 Verify;
- same_id_conflict 阻断;
- 来源/目标表直接并发写受锁阻断;
- Copy 中途异常时业务行和 evidence 一起回滚;
- Copy 成功但响应丢失后的精确重放;
- 响应丢失后直接修改来源时拒绝重放;
- Verify 幂等重放不增加 operation version。
本轮 TAX 专项验证已具备 pnpm、Docker 与 PostgreSQL 16,但没有重跑本节完整 Copy/Verify 套件,因此该套件继续按独立运行报告裁定,不能由 TAX 专项 Testcontainers 结果替代。
## 11. Windows 本地隔离演练工具
`scripts/local-rehearsal/` 提供:
```text
Invoke-AnenreoEnvironmentCheck.ps1
Invoke-AnenreoBatch005RRehearsal.ps1
Export-AnenreoEvidence.ps1
docker-compose.postgres16.yml
rehearsal.variables.example.txt
README.zh-CN.md
```
工具默认:
- 不联网、不自动安装依赖、不拉取镜像;
- 要求本地已有仓库锁定 Node/pnpm、`node_modules` 和 `postgres:16-alpine` 镜像;
- 清除演练子进程继承的 `DATABASE_URL` 与 `PG*`,只允许 Testcontainers 生成的一次性连接;
- 保留每条命令、退出码和日志;
- 无论环境检查、测试还是离线安装检查失败,都尝试导出脱敏 evidence ZIP;
- 拒绝符号链接/reparse point,拒绝把 evidence 输出到源码目录内部;
- 不执行 Cutover、业务库 Copy/Verify、Provider 调用或外部消息。
本机可运行 PowerShell;是否完成此演练仍以该工具生成的同轮脱敏 evidence ZIP 为准,不以工具存在推导。
## 12. 当前可证明结果与下一步
当前环境已证明:
- 迁移实际 TypeScript 纯逻辑 15/15;
- Batch 005R 架构门禁 10/10;
- Batch 004 维护运行时回归 12/12;
- 迁移基础设施 12 个实际源码文件严格子集 typecheck 为 0 diagnostics;
- 迁移装配层 14 个实际源码文件严格子集 typecheck 为 0 diagnostics;
- 变化 TypeScript 纯语法转译和 MJS `node --check` 通过。
严格子集 typecheck 使用当前容器 TypeScript 5.8.3 和显式的既有依赖桩,不替代仓库锁定 TypeScript 5.9.3 的全量 typecheck。PostgreSQL、Jest、Vitest、Playwright、完整 build 和 PowerShell 演练均仍为 `not_run`。
下一步是在开发者本地隔离环境运行演练工具,上传脱敏 evidence ZIP。只有 PostgreSQL 16 的 0000~0010、Copy、独立 Verify、锁竞争、回滚和重放全部通过,才允许准备 MEM-001、PC 首页、Capture、Seed、Export 的 Cutover;仍不得直接在业务数据库试迁移或退役旧 v1。