12 KiB
ADR-0483:密封 Reconciliation Bundle 的有界数据域计划
- 状态:Accepted(D-390 已实现)
- 日期:2026-08-21
- 关联 RFC:QL-RFC-0001 D-05、D-06、D-17、D-64、D-87、D-184、D-259、D-383、D-389、D-390
- 关联 ADR:ADR-0064、ADR-0094、ADR-0095、ADR-0194、ADR-0201、ADR-0314、ADR-0315、ADR-0482
- 细化:ADR-0482 第 3、5、7 节的 terminal asset layout 与只读消费者契约
背景
ADR-0482 已把 stopped reconciliation_required 的 target、Legacy source、recovery 和 lineage 冻结为不可变 bundle,但
reconciliation_captured 只证明原始字节可恢复,不说明两侧哪些数据域发生变化、哪些事实可映射、哪些只能保留或人工裁决。直接从
capture 跳到 import 会把“证据保全”错误提升成写 authority,并且无法回答 3.0 Run、Secret、Plugin Package、AI、identity 与 audit
如何安全降级到 2.x。
当前 bundle 还使用 target-main、target-wal 等逻辑名作为实际文件名。SQLite 只识别 <main>-wal/-shm/-journal,因此下游若要
检查一致 snapshot,只能再复制一整份数据库或修改 bundle。前者会在路由器、NAS 和小闪存设备上制造不可接受的第二次空间放大;后者
破坏 ADR-0482 的不可变承诺。D-390 必须先修正物理读取契约,再建立严格只读、固定内存和固定输出的领域计划。
决策
1. Capture schema v2 使用 SQLite 可识别的固定物理名并密封
manifest 继续只保存固定逻辑名,不保存输入绝对路径;实际 payload 使用固定映射:
| 逻辑名 | 物理名 |
|---|---|
target-main |
target.sqlite |
target-wal/-shm/-journal |
target.sqlite-wal/-shm/-journal |
legacy-main |
legacy.sqlite |
legacy-wal/-shm/-journal |
legacy.sqlite-wal/-shm/-journal |
recovery-main |
recovery.sqlite |
capture manifest/receipt 升为 schema v2,并把 activation 中的 sourceSha256、targetSha256 纳入内容无关 lineage projection 和
manifest。receipt 发布后、instance head 推进前,所有 asset 从 0600 收敛为 0400,assets directory 从 0700 收敛为 0500,
并 fsync 文件、目录及父目录。部分 chmod、receipt-response-loss 和 head-response-loss 均由同一 commit 收敛;terminal verify 只接受
完整密封状态。3.0 尚未发布,本 ADR 不为内部 alpha schema v1 增加双格式兼容或自动重写。
2. 只允许两种零复制 SQLite 读取方式
- main 无
-wal/-shm/-journal:使用file:...?...immutable=1与readOnly; -wal与-shm同时存在、无-journal:使用普通readOnly,依赖密封 mode 阻止 SQLite 改写 shm。
存在 hot journal、WAL/SHM 不配对、额外 sidecar、mode/owner/link/hash 漂移时,不打开数据库、不尝试 checkpoint/recovery,也不把 main
当作完整 snapshot;该数据库 inventory 固定为 manual_required。每次打开前后重验 manifest 中全部 bytes/hash 和 stat,任何变化失败
关闭。handle 固定 defensive、trusted_schema=OFF、query_only=ON、temp_store=MEMORY、mmap_size=0,禁止 extension、attach、
vacuum、DDL、DML 和网络。
3. 增加独立 plan prepare、commit、verify
既有 ql3-local-deploy 增加:
local.deployment.reconciliation.plan.prepare
local.deployment.reconciliation.plan.commit
local.deployment.reconciliation.plan.verify
调用方显式提供独立私有 planRoot、UUID planId、captureRoot/captureId、exact bundleDigest、当前 instance head digest、
legacyTimezone|null 和时间。prepare 验证密封 bundle 与 reconciliation_captured head,以 CAS 推进为
reconciliation_plan_prepared,并在 <planRoot>/<planId> no-replace 发布固定 intent。planRoot 不得位于 deployment root、capture
directory 或任一 SQLite asset 内。
commit 只读取密封 bundle,发布固定 schema plan、terminal receipt,再以 plan digest 把 head 推进为 reconciliation_planned。
verify 只重验 terminal plan、receipt、bundle binding 与 head,不打开 SQLite、不创建/清理文件。prepare/commit response loss、plan/receipt/
head 崩溃窗口均 exact replay;不同 command 不能复用 planId。
4. Plan 只提供固定领域 summary 和 disposition
plan 固定包含以下有序领域,不保存 row value、command、Secret、credential、日志、业务标识或单条对象名称:
schema_lineage:两侧 catalog、migration/capability 与未知对象摘要;automation:Legacy Crontabs 与 3.0 Task/Trigger/adoption ledger 的计数和基线漂移;secret_and_config:Legacy Envs/Auths 与 3.0 Secret/data-directory adoption 的计数和 custody 缺口;run_history:Run/Attempt/Event/Step 等不可逆历史;plugin_package:install/materialization/publication/lifecycle/quarantine;ai_and_tool:provider、prompt、Tool execution/output/key binding;identity_policy_audit:Project、RoleBinding、credential、pepper、approval 与 audit;unknown:不在固定分类中的 table/schema facts。
每个领域只返回 bounded counts、inventory digest 和以下 disposition 之一:
aligned | legacy_changed | target_changed | diverged |
target_only | manual_required | unsupported
aligned 也不授予写入;target_only 表示必须保留 3.0 bundle 或由后续显式 exporter 处理;manual_required/unsupported 禁止自动计划。
只要未知 schema、hot journal、不完整 sidecar、未映射 Legacy facts、active/inconclusive Run 或 credential/Secret custody 缺口存在,整体
outcome 必须为 manual_required。本 ADR 不产生 import_ready、rollback_ready 或 legacy_ready。
5. Diff 使用已提交 baseline,不猜测语义等价
capture manifest 的 source/target baseline SHA-256 与 captured asset set 判定数据库级 unchanged|changed。target 内既有
QingLong3LegacyAdoptions 和 QingLong3LegacyDataDirectoryAdoptions 只提供已提交 adoption baseline;Task/Trigger current revision、
Secret version 和各领域 table count/digest 只用于保守分类。名称相同、数量相同或 JSON 看起来相似都不能自动视为等价。
Legacy timezone 是显式 reviewed input;若与可证明的 adopted trigger timezone 不一致则 manual_required。D-390 不解密 Secret、不读取
credential/token、不执行 Legacy command,也不构造 2.x SQL。逐对象 private diagnostics、人工选择与目标 adapter 由后续 ADR 定义,必须
消费 exact plan digest。
6. 低资源与 package 边界
实现继续内聚在 @qinglong/local-owner-cli/src/deployment/reconciliation/ 的 sealed-bundle/ 与 planning/ 子目录,不新增 workspace
package、production dependency、binary、daemon、timer、watcher、listener、socket 或后台 retry。不得把新文件平铺到 package src/
根,也不得从 deployment planning import Local SQLite mutation/adoption authority。
内存上限为单个 SQLite handle、64 KiB hash buffer、固定八领域数组和不超过 64 KiB 的 plan builder。Edge/Standalone SQLite cache 分别不超过 2/8 MiB;schema object 上限 4,096,table 上限 512,领域 row count 只保留安全整数和总量,不把对象列表放入内存。plan 文件不超过 64 KiB。Cluster/PostgreSQL 不消费 Local bundle 或 plan。
被拒绝的替代方案
为分析再复制 target 与 Legacy 数据库
拒绝。最坏会在 capture 已占两份数据库后再增加两份,直接破坏路由器和小闪存设备的空间模型。
直接以 immutable 打开带 WAL 的 main
拒绝。SQLite immutable 会忽略 WAL,可能静默丢失已经提交但未 checkpoint 的事实。
让 readonly SQLite 使用可写 shm
拒绝。真实验证表明 readonly handle 仍可能改写 -shm;只有密封资产才能保持 bundle hash 不变。
从表名相似自动生成回灌 SQL
拒绝。相同名称不证明 Project、revision、Policy、Secret custody、append-only history 或幂等身份等价。
把完整 row diff 写入 plan
拒绝。会泄漏敏感内容、产生无界内存/文件,并把 inventory 偷换成未审批的迁移 payload。
验收条件
- Capture v2 固定物理名、0400/0500 密封、baseline digest 与全部 crash replay 可验证;v1 不被静默接受。
- main-only immutable 与 WAL+SHM readonly 在 Linux/Docker 中可读取且前后 bytes/hash/stat 完全不变;hot journal/unpaired sidecar 零
SQLite open 并稳定
manual_required。 - plan prepare 只接受 exact
reconciliation_capturedbundle/head 并建立唯一 CAS fence;restart/rollback/第二计划不能越过。 - commit 产生固定八领域、固定 disposition、≤64 KiB 的内容无关 plan/receipt;unknown、Secret/credential、Run 与未映射事实保守分类。
- plan/receipt/head 各崩溃窗口和 response loss exact replay;verify 完全只读且不打开 SQLite。
- 无 import、DML、checkpoint、service、Docker/init/network 副作用,无新增 package/dependency/常驻对象或跨包写 authority import。
- Edge 固定 2 MiB cache、Standalone 8 MiB cache;完整 Local Owner/backend/package、架构、release、十四档 artifact 与真实 Docker readonly rehearsal 通过,基础 Edge closure 不增长。
实现与验证证据
D-390 已在既有 @qinglong/local-owner-cli/src/deployment/reconciliation/ 内实现。sealed-bundle/reader.ts 只接受经 terminal
validator 验证的密封 capture:main-only 走 immutable readonly,WAL+SHM 完整配对走普通 readonly;hot journal、sidecar 不配对或
任一 stat/hash/mode 漂移均在 SQLite open 前失败关闭。planning/ 以固定 contract、inventory、prepare/commit/verify 将 instance head
从 reconciliation_captured CAS 推进到 reconciliation_plan_prepared、再推进到 reconciliation_planned。plan 只包含固定八领域的
有界计数、digest、disposition 和保守 outcome,不保存表名、路径、row value、Secret、credential、命令或日志,也不产生 import authority。
实现没有新增 workspace package、production dependency、binary、daemon、listener、timer、watcher 或网络访问。workspace 仍为 18
packages,singleSourcePackages=[]、shallowSourcePackages=[];Local Owner 为 146 source / 145 nested / 1 root binary entry,新增的
5 个源文件全部进入 deployment/reconciliation/sealed-bundle|planning/,没有回到 src/ 根平铺。Edge/Standalone cache 固定为
2/8 MiB,hash buffer 与 plan 上限均为 64 KiB,schema/table 上限为 4,096/512。
验收结果:reconciliation 聚焦套件 24 total / 22 pass / 2 conditional Docker skip / 0 fail;真实 Linux/Docker main-only 与
WAL+SHM readonly/hash-stability rehearsal 2/2;完整 Local Owner 246 total / 239 pass / 7 conditional skip / 0 fail;tracked
backend 1540 total / 1538 pass / 2 conditional skip / 0 fail;18-package clean build/逐包测试通过。Edge import、Cluster dependency、
package boundary、service-manager bridge、Local image、image release、release version 与 deployment-lock surface 审计均 compatible。
十四档 artifact audit 均 compatible。基础 Edge/Standalone 仍为 2,611,978 / 2,612,056 bytes、319 files、58 modules;Adopted
仍为 2,831,713 / 2,831,836 bytes、339 files、59 modules;Application+AI 为 4,529,710 / 4,529,842 bytes、516 files、
144 modules;MCP 为 7,337,910 / 7,338,018 bytes、805 files、228 modules。一次性 plan authority 未进入基础常驻闭包,也没有被
Cluster/PostgreSQL 消费。
未包含
- 逐对象明细分页、人工冲突选择和审批;
- 任何 Legacy/target 写入、Secret 解密、credential 导出或服务 restart;
- 自动 checkpoint、hot journal recovery 或损坏数据库修复;
- Cluster/PostgreSQL/Kubernetes reconciliation;
- 固定物理 Edge/NAS 的断电、FTL 写放大与介质销毁证明。